diff --git a/Gruntfile.js b/Gruntfile.js new file mode 100644 index 0000000..3ef258d --- /dev/null +++ b/Gruntfile.js @@ -0,0 +1,173 @@ +/*global module:false*/ +module.exports = function(grunt) { + var paths = { + make : function (seg, trailing) { + var args = Array.prototype.slice.call(arguments); + trailing = ( typeof args[args.length - 1] === 'boolean' ) ? args.pop() : true; + //Build path + var sep = '/'; + var path = args.join(sep); + if ( trailing ) { + path += sep; + } + return path; + }, + makeJS : function(base) { + return { + base : paths.make(base, paths.js), + dev : paths.make(base, paths.dev), + dist : paths.make(base, paths.dist) + }; + }, + sass : 'sass', + css : 'css', + js : 'js', + dev : 'dev', + dist : 'dist', + client : 'client', + themes : 'themes' + }; + + var files = { + client : { + sass : [{ + expand : true, + cwd : paths.make(paths.client, paths.sass), + dest : paths.make(paths.client, paths.css), + src : ['**/*.scss'], + ext : '.css' + }], + js : [{ + expand : true, + cwd : 'client/js/dev/', + dest : 'client/js/dist/', + src : ['**/*.js'], + }] + }, + themes : { + sass : [{ + expand : true, + cwd : 'themes/', + src : ['*/**/*.scss'], + dest : 'css/', + srcd : 'sass/', + ext : '.css', + rename : function(dest, matchedSrcPath, options) { + var path = [options.cwd, matchedSrcPath.replace(options.srcd, dest)].join(''); + return path; + } + }] + } + }; + + // Project configuration. + grunt.initConfig({ + // Metadata. + pkg : grunt.file.readJSON('package.json'), + banner : '/*! <%= pkg.title || pkg.name %> - v<%= pkg.version %> - ' + '<%= grunt.template.today("yyyy-mm-dd") %>\n' + '<%= pkg.homepage ? "* " + pkg.homepage + "\\n" : "" %>' + '* Copyright (c) <%= grunt.template.today("yyyy") %> <%= pkg.author.name %>;' + ' Licensed <%= pkg.license %> */\n', + // Task configuration. + jshint : { + options : { + curly : true, + eqeqeq : true, + immed : true, + latedef : true, + newcap : false, + noarg : true, + sub : true, + undef : true, + unused : true, + boss : true, + eqnull : true, + browser : true, + jquery : true, + globals : {} + }, + gruntfile : { + options : { + node : true + }, + src : 'Gruntfile.js' + }, + client : { + src : ['client/js/dev/**/*.js'] + }, + themes : { + options : { + globals : { + 'SLB' : true + } + }, + src : ['themes/**/*.js'] + } + }, + uglify : { + options : { + mangle: false, + report: 'min' + }, + client : { + files : files.client.js + } + }, + sass : { + options : { + outputStyle : 'compressed', + }, + client : { + files : files.client.sass + }, + themes : { + options : { + includePaths : require('node-bourbon').includePaths + }, + files : files.themes.sass + } + }, + phplint : { + options : { + phpArgs : { + '-lf': null + } + }, + core : ['*.php'], + includes : ['includes/**/*.php'] + }, + watch : { + gruntfile : { + files : '<%= jshint.gruntfile.src %>', + tasks : ['jshint:gruntfile'] + }, + client_js : { + files : '<%= jshint.client.src %>', + tasks : ['jshint:client'] + }, + client_sass : { + files : ['client/sass/**/*.scss'], + tasks : ['sass:client'] + }, + themes_js : { + files : '<%= jshint.themes.src %>', + tasks : ['jshint:themes'] + }, + themes_sass : { + files : ['themes/*/sass/*.scss'], + tasks : ['sass:themes'] + } + } + }); + + // These plugins provide necessary tasks. + grunt.loadNpmTasks('grunt-contrib-concat'); + grunt.loadNpmTasks('grunt-contrib-uglify'); + grunt.loadNpmTasks('grunt-contrib-qunit'); + grunt.loadNpmTasks('grunt-contrib-jshint'); + grunt.loadNpmTasks('grunt-sass'); + grunt.loadNpmTasks('grunt-phplint'); + grunt.loadNpmTasks('grunt-contrib-watch'); + + // Default Tasks + grunt.registerTask('build', ['phplint', 'jshint', 'uglify', 'sass']); + grunt.registerTask('watch_client', ['watch:client_js', 'watch:client_sass']); + grunt.registerTask('watch_themes', ['watch:themes_js', 'watch:themes_sass']); +}; diff --git a/client/css/admin.css b/client/css/admin.css index a6f6b5c..6c41339 100644 --- a/client/css/admin.css +++ b/client/css/admin.css @@ -1 +1 @@ -.slb_section_head{display:block;padding:2em 0 0}.slb_option_item .block{display:inline-block}.slb_option_item label.title{width:200px;padding:10px}.slb_option_item .input{font-size:11px;line-height:20px;margin-bottom:9px;padding:8px 10px}.slb_option_item .input select{min-width:12em}.slb_notice{color:#f00;font-weight:bold}.slb .columns-2{margin-right:300px}.slb .columns-2 .postbox-container{float:left;width:100%}.slb .columns-2 .content-secondary{margin-right:-300px;width:280px;float:right} +.slb_section_head {display:block;padding:2em 0 0;}.slb_option_item .block {display:inline-block;}.slb_option_item label.title {width:200px;padding:10px;}.slb_option_item .input {font-size:11px;line-height:20px;margin-bottom:9px;padding:8px 10px;}.slb_option_item .input select {min-width:12em;}.slb_notice {color:#ff0000;font-weight:bold;}.slb .columns-2 {margin-right:300px;}.slb .columns-2 .postbox-container {float:left;width:100%;}.slb .columns-2 .content-secondary {margin-right:-300px;width:280px;float:right;} \ No newline at end of file diff --git a/client/css/app.css b/client/css/app.css index 27ab4b2..105df66 100644 --- a/client/css/app.css +++ b/client/css/app.css @@ -1,3 +1 @@ -html.slb_overlay object, html.slb_overlay embed, html.slb_overlay iframe { - visibility: hidden; -} +html.slb_overlay object ,html.slb_overlay embed ,html.slb_overlay iframe {visibility:hidden;}html.slb_overlay #slb_viewer_wrap object ,html.slb_overlay #slb_viewer_wrap embed ,html.slb_overlay #slb_viewer_wrap iframe {visibility:visible;} \ No newline at end of file diff --git a/client/js/lib.admin.js b/client/js/dev/lib.admin.js similarity index 90% rename from client/js/lib.admin.js rename to client/js/dev/lib.admin.js index 3afb6bf..cb941fe 100644 --- a/client/js/lib.admin.js +++ b/client/js/dev/lib.admin.js @@ -1,31 +1,33 @@ -/** - * Admin - * @package Simple Lightbox - * @subpackage Admin - * @author Archetyped - */ - -if ( jQuery ){(function ($) { - -if ( !SLB || !SLB.attach ) { - return false; -} - -var Admin = { - /** - * Initialization routines - */ - init: function() { - if ( postboxes ) { - postboxes.add_postbox_toggles(pagenow); - } - }, -} - -SLB.attach('Admin', Admin); - -$(document).ready(function() { - SLB.Admin.init(); -}); - +/** + * Admin + * @package Simple Lightbox + * @subpackage Admin + * @author Archetyped + */ + +/* global SLB, postboxes, pagenow */ + +if ( jQuery ){(function ($) { + +if ( !SLB || !SLB.attach ) { + return false; +} + +var Admin = { + /** + * Initialization routines + */ + init: function() { + if ( postboxes ) { + postboxes.add_postbox_toggles(pagenow); + } + }, +}; + +SLB.attach('Admin', Admin); + +$(document).ready(function() { + SLB.Admin.init(); +}); + })(jQuery);} \ No newline at end of file diff --git a/client/js/lib.core.js b/client/js/dev/lib.core.js similarity index 85% rename from client/js/lib.core.js rename to client/js/dev/lib.core.js index e4625af..03eff12 100644 --- a/client/js/lib.core.js +++ b/client/js/dev/lib.core.js @@ -1,590 +1,595 @@ -/** - * Core - * @package SLB - * @author Archetyped - */ -if ( jQuery ){(function($) { - -/** - * Extendible class - * Adapted from John Resig - * @link http://ejohn.org/blog/simple-javascript-inheritance/ - */ -var c_init = false; -var Class = function() {}; - -Class.extend = function(members) { - var _super = this.prototype; - - //Copy instance to prototype - c_init = true; - var proto = new this(); - c_init = false; - - var val; - //Scrub prototype objects (Decouple from super class) - for ( var name in proto ) { - if ( $.isPlainObject(proto[name]) ) { - val = $.extend({}, proto[name]); - proto[name] = val; - } - } - - //Copy members - for ( var name in members ) { - //Evaluate function members (if overwriting super class method) - if ( 'function' == typeof members[name] && 'function' == typeof _super[name] ) { - proto[name] = (function(name, fn) { - return function() { - //Cache super variable - var tmp = this._super; - //Set variable to super class method - this._super = _super[name]; - //Call method - var ret = fn.apply(this, arguments); - //Restore super variable - this._super = tmp; - //Return value - return ret; - } - })(name, members[name]); - } else { - val = members[name]; - if ( $.isPlainObject(members[name]) ) { - val = $.extend({}, members[name]); - } - proto[name] = val; - } - } - - //Constructor - function Class() { - if ( !c_init ) { - //Private init - if ( this._init ) { - this._init.apply(this, arguments); - } - //Main Constructor - if ( this._c ) { - this._c.apply(this, arguments); - } - } - } - - - //Populate new prototype - Class.prototype = proto; - - //Set constructor - Class.prototype.constructor = Class; - - Class.extend = arguments.callee; - - //Return function - return Class; -}; - -/* Base */ -var Base = { - /* Properties */ - - base: false, - _parent: null, - prefix: 'slb', - - /* Methods */ - - /** - * Constructor - */ - _init: function() { - this._set_parent(); - }, - - _set_parent: function(p) { - if ( typeof p != 'undefined' ) - this._parent = p; - this.util._parent = this; - }, - - /** - * Attach member to object - * @param string name Member name - * @param mixed Member data - * > obj: Member inherits from base object - * > other: Simple data object - */ - attach: function(member, data, simple) { - simple = ( typeof simple == undefined ) ? false : !!simple; - if ( $.type(member) == 'string' && $.isPlainObject(data) ) { - //Add initial member - var obj = {}; - if ( simple ) { - //Simple object - obj[member] = $.extend({}, data); - $.extend(this, obj); - } else { - //Add new instance object - data['_parent'] = this; - var c = this.Class.extend(data); - this[member] = new c(); - } - } - }, - - /** - * Get parent object - * @return obj Parent object - */ - get_parent: function() { - return this._parent; - }, - - /** - * Utility methods - */ - util: { - /* Properties */ - - _base: null, - _parent: null, - - /* Constants */ - - string: 'string', - bool: 'boolean', - array: 'array', - obj: 'object', - func: 'function', - num: 'number', - - /* Methods */ - - get_base: function() { - if ( !this._base ) { - var p = this.get_parent(); - var p_last = null; - //Iterate through parents - while ( !p.base && p_last != p && p._parent ) { - p_last = p; - p = p._parent; - } - //Set base - this._base = p; - } - return this._base; - }, - - get_parent: function() { - return this._parent; - }, - - /** - * Retrieve valid separator - * If supplied argument is not a valid separator, use default separator - * @param string (optional) sep Separator text - * @return string Separator text - */ - get_sep: function(sep) { - return ( this.is_string(sep, false) ) ? sep : '_'; - }, - - /** - * Retrieve prefix - * @return string Prefix - */ - get_prefix: function() { - return ( this.is_string(this.get_parent().prefix) ) ? this.get_parent().prefix : ''; - }, - - /** - * Check if string is prefixed - */ - has_prefix: function(val, sep) { - return ( this.is_string(val) && val.indexOf(this.get_prefix() + this.get_sep(sep)) === 0 ); - }, - - /** - * Add Prefix to a string - * @param string val Value to add prefix to - * @param string sep (optional) Separator (Default: `_`) - * @param bool (optional) once If text should only be prefixed once (Default: TRUE) - */ - add_prefix: function(val, sep, once) { - //Validate - if ( !this.is_string(val) ) { - //Return prefix if value to add prefix to is empty - return this.get_prefix(); - } - sep = this.get_sep(sep); - if ( !this.is_bool(once) ) { - once = true; - } - - return ( once && this.has_prefix(val, sep) ) ? val : [this.get_prefix(), val].join(sep); - }, - - /** - * Remove Prefix from a string - * @param string val Value to add prefix to - * @param string sep (optional) Separator (Default: `_`) - * @param bool (optional) once If text should only be prefixed once (Default: true) - */ - remove_prefix: function(val, sep, once) { - //Validate parameters - if ( !this.is_string(val, true) ) { - return val; - } - //Default values - sep = this.get_sep(sep); - if ( !this.is_bool(once) ) { - once = true; - } - //Check if string is prefixed - if ( this.has_prefix(val, sep) ) { - //Remove prefix - var prfx = this.get_prefix() + sep; - do { - val = val.substr(prfx.length); - } while ( !once && this.has_prefix(val, sep) ); - } - return val; - }, - - /* - * Get attribute name - * @param string val Attribute's base name - */ - get_attribute: function(val) { - //Setup - var sep = '-'; - var top = 'data'; - //Validate - var pre = [top, this.get_prefix()].join(sep); - if ( !this.is_string(val, false) ) { - return pre; - } - //Process - if ( val.indexOf(pre + sep) == -1 ) { - val = [pre, val].join(sep); - } - return val; - }, - - /* Request */ - - /** - * Retrieve valid context - * @return array Context - */ - get_context: function() { - //Valid context - var b = this.get_base(); - if ( !$.isArray(b.context) ) - b.context = []; - //Return context - return b.context; - }, - - /** - * Check if a context exists in current request - * If multiple contexts are supplied, result will be TRUE if at least ONE context exists - * - * @param string|array ctx Context to check for - * @return bool TRUE if context exists, FALSE otherwise - */ - is_context: function(ctx) { - var ret = false; - //Validate context - if ( typeof ctx == 'string' ) - ctx = [ctx]; - if ( $.isArray(ctx) && this.arr_intersect(this.get_context(), ctx).length ) { - ret = true; - } - return ret; - }, - - /* Helpers */ - - is_set: function(value) { - return ( $.type(value) != 'undefined' ) ? true : false; - }, - - is_type: function(value, type, nonempty) { - var ret = false; - if ( this.is_set(value) && null != value && this.is_set(type) ) { - switch ( $.type(type) ) { - case this.func: - ret = ( value instanceof type ) ? true : false; - break; - case this.string: - ret = ( $.type(value) == type ) ? true : false; - break; - default: - ret = false; - break; - } - } - - //Validate empty values - if ( ret && ( $.type(nonempty) != this.bool || nonempty ) ) { - ret = !this.is_empty(value); - } - return ret; - }, - - is_string: function(value, nonempty) { - return this.is_type(value, this.string, nonempty); - }, - - is_array: function(value, nonempty) { - return ( this.is_type(value, this.array, nonempty) ); - }, - - is_bool: function(value) { - return this.is_type(value, this.bool, false); - }, - - is_obj: function(value, nonempty) { - return this.is_type(value, this.obj, nonempty); - }, - - is_func: function(value) { - return this.is_type(value, this.func, false); - }, - - /** - * Checks if an object has a method - * @param obj Object to check - * @param string|array Names of methods to check for - * @return bool TRUE if method(s) exist, FALSE otherwise - */ - is_method: function(obj, value) { - var ret = false; - if ( this.is_string(value) ) { - value = [value]; - } - if ( this.in_obj(obj, value) ) { - var t = this; - $.each(value, function(idx, val) { - ret = ( t.is_func(obj[val]) ) ? true : false; - return ret; - }); - } - return ret; - }, - - is_num: function(value, nonempty) { - return ( this.is_type(value, this.num, nonempty) && !isNaN(value) ); - }, - - is_int: function(value, nonempty) { - return ( this.is_num(value, nonempty) && Math.floor(value) === value ); - }, - - is_scalar: function(value, nonempty) { - return ( this.is_num(value, nonempty) || this.is_string(value, nonempty) || this.is_bool(value, nonempty) ); - }, - - /** - * Checks if value is empty - * @param mixed value Value to check - * @param string type (optional) Data type - * @return bool TRUE if value is empty, FALSE if not empty - */ - is_empty: function(value, type) { - var ret = false; - //Initial check for empty value - if ( !this.is_set(value) || null === value || false === value ) { - ret = true; - } else { - //Validate type - if ( !this.is_set(type) ) { - type = $.type(value); - } - //Type-based check - if ( this.is_type(value, type, false) ) { - switch ( type ) { - case this.string: - case this.array: - if ( value.length == 0 ) { - ret = true; - } - break; - case this.obj: - //Only evaluate literal objects - ret = ( $.isPlainObject(value) && !$.map(value, function(v, key) { return key; }).length ); - break; - case this.num: - ret = ( value === 0 ); - break; - } - } else { - ret = true; - } - } - return ret; - }, - - /** - * Check if object is a jQuery.Promise instance - * Will also match (but not guarantee) jQuery.Deferred instances - * @return bool TRUE if object is Promise/Deferred, FALSE otherwise - */ - is_promise: function(obj) { - return ( this.is_obj(obj) && this.is_method(obj, ['then', 'done', 'always', 'fail', 'pipe']) ) - }, - - /** - * Check if object is a jQuery.Deferred instance - */ - is_deferred: function(obj) { - return ( this.is_promise(obj) && this.is_method(obj, ['resolve', 'reject', 'promise'])); - }, - - /** - * Validate specified value's data type and return default value if necessary - * Data type of default value is used to determine data type - * @param mixed val Value to check - * @param mixed def Default value - * @return mixed Valid value - */ - validate: function(val, def) { - return ( this.is_type(val, def, true) ) ? val : def; - }, - - /** - * Return formatted string - */ - format: function(fmt, val) { - if ( !this.is_string(fmt) ) { - return ''; - } - var params = [], - ph = '%s'; - //Stop processing if no replacement values specified or format string contains no placeholders - if ( arguments.length < 2 || fmt.indexOf(ph) == -1 ) { - return fmt; - } - //Get replacement values - params = Array.prototype.slice.call(arguments, 1); - - //Replace placeholders in string with parameters - - //Replace all placeholders at once if single parameter set - if ( params.length == 1 ) { - fmt = fmt.replace(ph, params[0].toString()); - } else { - var idx = 0, - len = params.length, - pos = 0; - while ( ( pos = fmt.indexOf(ph) ) && idx < len ) { - fmt = fmt.substr(0, pos) + params[idx].toString() + fmt.substr(pos + ph.length); - idx++; - } - //Remove any remaining placeholders - fmt = fmt.replace(ph, ''); - } - return fmt; - }, - - /** - * Checks if key(s) exist in an object - * @param object obj Object to check - * @param string|array key Key(s) to check for in object - * @return bool TRUE if key(s) exist in object, FALSE otherwise - */ - in_obj: function(obj, key, all) { - //Validate - if ( !this.is_bool(all) ) { - all = true; - } - if ( this.is_string(key) ) { - key = [key]; - } - var ret = false; - if ( this.is_obj(obj) && this.is_array(key) ) { - var val; - for ( var x = 0; x < key.length; x++ ) { - val = key[x]; - ret = ( this.is_string(val) && ( val in obj ) ) ? true : false; - //Stop processing if conditions have been met - if ( ( !all && ret ) || ( all && !ret ) ) { - break; - } - } - } - return ret; - }, - - /** - * Find common elements of 2 arrays - * @param array arr1 First array - * @param array arr2 Second array - * @return array Elements common to both arrays - */ - arr_intersect: function(arr1, arr2) { - var ret = []; - if ( arr1 == arr2 ) { - return arr2; - } - if ( !$.isArray(arr2) || !arr2.length || !arr1.length ) { - return ret; - } - //Compare elements in arrays - var a1; - var a2; - var val; - if ( arr1.length < arr2.length ) { - a1 = arr1; - a2 = arr2; - } else { - a1 = arr2; - a2 = arr1; - } - - for ( var x = 0; x < a1.length; x++ ) { - //Add mutual elements into intersection array - val = a1[x]; - if ( a2.indexOf(val) != -1 && ret.indexOf(val) == -1 ) - ret.push(val); - } - - //Return intersection results - return ret; - } - } -}; -var SLB_Base = Class.extend(Base); - -//Init global object -var Core = { - /* Properties */ - - base: true, - context: [], - - /** - * New object initializer - * @var obj - */ - Class: SLB_Base, - - /* Methods */ - - /** - * Setup client - * Set variables, DOM, etc. - */ - setup_client: function() { - /* Quick Hide */ - $('html').addClass(this.util.get_prefix()); - } -}; -var SLB_Core = SLB_Base.extend(Core); - -this.SLB = new SLB_Core(); - -SLB.setup_client(); - +/** + * Core + * @package SLB + * @author Archetyped + */ +if ( jQuery ){(function($) { + +/** + * Extendible class + * Adapted from John Resig + * @link http://ejohn.org/blog/simple-javascript-inheritance/ + */ +var c_init = false; +var Class = function() {}; + +Class.extend = function(members) { + var _super = this.prototype; + + //Copy instance to prototype + c_init = true; + var proto = new this(); + c_init = false; + + var val, name; + //Scrub prototype objects (Decouple from super class) + for ( name in proto ) { + if ( $.isPlainObject(proto[name]) ) { + val = $.extend({}, proto[name]); + proto[name] = val; + } + } + + //Copy members + var make_handler = function(nm, fn) { + return function() { + //Cache super variable + var tmp = this._super; + //Set variable to super class method + this._super = _super[nm]; + //Call method + var ret = fn.apply(this, arguments); + //Restore super variable + this._super = tmp; + //Return value + return ret; + }; + }; + for ( name in members ) { + //Evaluate function members (if overwriting super class method) + if ( 'function' === typeof members[name] && 'function' === typeof _super[name] ) { + proto[name] = make_handler(name, members[name]); + } else { + val = members[name]; + if ( $.isPlainObject(members[name]) ) { + val = $.extend({}, members[name]); + } + proto[name] = val; + } + } + + //Constructor + function Class() { + if ( !c_init ) { + //Private init + if ( this._init ) { + this._init.apply(this, arguments); + } + //Main Constructor + if ( this._c ) { + this._c.apply(this, arguments); + } + } + } + + + //Populate new prototype + Class.prototype = proto; + + //Set constructor + Class.prototype.constructor = Class; + + Class.extend = this.extend; + + //Return function + return Class; +}; + +/* Base */ +var Base = { + /* Properties */ + + base: false, + _parent: null, + prefix: 'slb', + + /* Methods */ + + /** + * Constructor + */ + _init: function() { + this._set_parent(); + }, + + _set_parent: function(p) { + if ( typeof p !== 'undefined' ) { + this._parent = p; + } + this.util._parent = this; + }, + + /** + * Attach member to object + * @param string name Member name + * @param mixed Member data + * > obj: Member inherits from base object + * > other: Simple data object + */ + attach: function(member, data, simple) { + simple = ( typeof simple === undefined ) ? false : !!simple; + if ( $.type(member) === 'string' && $.isPlainObject(data) ) { + //Add initial member + var obj = {}; + if ( simple ) { + //Simple object + obj[member] = $.extend({}, data); + $.extend(this, obj); + } else { + //Add new instance object + data['_parent'] = this; + var C = this.Class.extend(data); + this[member] = new C(); + } + } + }, + + /** + * Get parent object + * @return obj Parent object + */ + get_parent: function() { + return this._parent; + }, + + /** + * Utility methods + */ + util: { + /* Properties */ + + _base: null, + _parent: null, + + /* Constants */ + + string: 'string', + bool: 'boolean', + array: 'array', + obj: 'object', + func: 'function', + num: 'number', + + /* Methods */ + + get_base: function() { + if ( !this._base ) { + var p = this.get_parent(); + var p_last = null; + //Iterate through parents + while ( !p.base && p_last !== p && p._parent ) { + p_last = p; + p = p._parent; + } + //Set base + this._base = p; + } + return this._base; + }, + + get_parent: function() { + return this._parent; + }, + + /** + * Retrieve valid separator + * If supplied argument is not a valid separator, use default separator + * @param string (optional) sep Separator text + * @return string Separator text + */ + get_sep: function(sep) { + return ( this.is_string(sep, false) ) ? sep : '_'; + }, + + /** + * Retrieve prefix + * @return string Prefix + */ + get_prefix: function() { + return ( this.is_string(this.get_parent().prefix) ) ? this.get_parent().prefix : ''; + }, + + /** + * Check if string is prefixed + */ + has_prefix: function(val, sep) { + return ( this.is_string(val) && val.indexOf(this.get_prefix() + this.get_sep(sep)) === 0 ); + }, + + /** + * Add Prefix to a string + * @param string val Value to add prefix to + * @param string sep (optional) Separator (Default: `_`) + * @param bool (optional) once If text should only be prefixed once (Default: TRUE) + */ + add_prefix: function(val, sep, once) { + //Validate + if ( !this.is_string(val) ) { + //Return prefix if value to add prefix to is empty + return this.get_prefix(); + } + sep = this.get_sep(sep); + if ( !this.is_bool(once) ) { + once = true; + } + + return ( once && this.has_prefix(val, sep) ) ? val : [this.get_prefix(), val].join(sep); + }, + + /** + * Remove Prefix from a string + * @param string val Value to add prefix to + * @param string sep (optional) Separator (Default: `_`) + * @param bool (optional) once If text should only be prefixed once (Default: true) + */ + remove_prefix: function(val, sep, once) { + //Validate parameters + if ( !this.is_string(val, true) ) { + return val; + } + //Default values + sep = this.get_sep(sep); + if ( !this.is_bool(once) ) { + once = true; + } + //Check if string is prefixed + if ( this.has_prefix(val, sep) ) { + //Remove prefix + var prfx = this.get_prefix() + sep; + do { + val = val.substr(prfx.length); + } while ( !once && this.has_prefix(val, sep) ); + } + return val; + }, + + /* + * Get attribute name + * @param string val Attribute's base name + */ + get_attribute: function(val) { + //Setup + var sep = '-'; + var top = 'data'; + //Validate + var pre = [top, this.get_prefix()].join(sep); + if ( !this.is_string(val, false) ) { + return pre; + } + //Process + if ( val.indexOf(pre + sep) === -1 ) { + val = [pre, val].join(sep); + } + return val; + }, + + /* Request */ + + /** + * Retrieve valid context + * @return array Context + */ + get_context: function() { + //Valid context + var b = this.get_base(); + if ( !$.isArray(b.context) ) { + b.context = []; + } + //Return context + return b.context; + }, + + /** + * Check if a context exists in current request + * If multiple contexts are supplied, result will be TRUE if at least ONE context exists + * + * @param string|array ctx Context to check for + * @return bool TRUE if context exists, FALSE otherwise + */ + is_context: function(ctx) { + var ret = false; + //Validate context + if ( typeof ctx === 'string' ) { + ctx = [ctx]; + } + if ( $.isArray(ctx) && this.arr_intersect(this.get_context(), ctx).length ) { + ret = true; + } + return ret; + }, + + /* Helpers */ + + is_set: function(value) { + return ( $.type(value) !== 'undefined' ) ? true : false; + }, + + is_type: function(value, type, nonempty) { + var ret = false; + if ( this.is_set(value) && null !== value && this.is_set(type) ) { + switch ( $.type(type) ) { + case this.func: + ret = ( value instanceof type ) ? true : false; + break; + case this.string: + ret = ( $.type(value) === type ) ? true : false; + break; + default: + ret = false; + break; + } + } + + //Validate empty values + if ( ret && ( $.type(nonempty) !== this.bool || nonempty ) ) { + ret = !this.is_empty(value); + } + return ret; + }, + + is_string: function(value, nonempty) { + return this.is_type(value, this.string, nonempty); + }, + + is_array: function(value, nonempty) { + return ( this.is_type(value, this.array, nonempty) ); + }, + + is_bool: function(value) { + return this.is_type(value, this.bool, false); + }, + + is_obj: function(value, nonempty) { + return this.is_type(value, this.obj, nonempty); + }, + + is_func: function(value) { + return this.is_type(value, this.func, false); + }, + + /** + * Checks if an object has a method + * @param obj Object to check + * @param string|array Names of methods to check for + * @return bool TRUE if method(s) exist, FALSE otherwise + */ + is_method: function(obj, value) { + var ret = false; + if ( this.is_string(value) ) { + value = [value]; + } + if ( this.in_obj(obj, value) ) { + var t = this; + $.each(value, function(idx, val) { + ret = ( t.is_func(obj[val]) ) ? true : false; + return ret; + }); + } + return ret; + }, + + is_num: function(value, nonempty) { + return ( this.is_type(value, this.num, nonempty) && !isNaN(value) ); + }, + + is_int: function(value, nonempty) { + return ( this.is_num(value, nonempty) && Math.floor(value) === value ); + }, + + is_scalar: function(value, nonempty) { + return ( this.is_num(value, nonempty) || this.is_string(value, nonempty) || this.is_bool(value, nonempty) ); + }, + + /** + * Checks if value is empty + * @param mixed value Value to check + * @param string type (optional) Data type + * @return bool TRUE if value is empty, FALSE if not empty + */ + is_empty: function(value, type) { + var ret = false; + //Initial check for empty value + if ( !this.is_set(value) || null === value || false === value ) { + ret = true; + } else { + //Validate type + if ( !this.is_set(type) ) { + type = $.type(value); + } + //Type-based check + if ( this.is_type(value, type, false) ) { + switch ( type ) { + case this.string: + case this.array: + if ( value.length === 0 ) { + ret = true; + } + break; + case this.obj: + //Only evaluate literal objects + ret = ( $.isPlainObject(value) && !$.map(value, function(v, key) { return key; }).length ); + break; + case this.num: + ret = ( value === 0 ); + break; + } + } else { + ret = true; + } + } + return ret; + }, + + /** + * Check if object is a jQuery.Promise instance + * Will also match (but not guarantee) jQuery.Deferred instances + * @return bool TRUE if object is Promise/Deferred, FALSE otherwise + */ + is_promise: function(obj) { + return ( this.is_obj(obj) && this.is_method(obj, ['then', 'done', 'always', 'fail', 'pipe']) ); + }, + + /** + * Check if object is a jQuery.Deferred instance + */ + is_deferred: function(obj) { + return ( this.is_promise(obj) && this.is_method(obj, ['resolve', 'reject', 'promise'])); + }, + + /** + * Validate specified value's data type and return default value if necessary + * Data type of default value is used to determine data type + * @param mixed val Value to check + * @param mixed def Default value + * @return mixed Valid value + */ + validate: function(val, def) { + return ( this.is_type(val, def, true) ) ? val : def; + }, + + /** + * Return formatted string + */ + format: function(fmt, val) { + if ( !this.is_string(fmt) ) { + return ''; + } + var params = [], + ph = '%s'; + //Stop processing if no replacement values specified or format string contains no placeholders + if ( arguments.length < 2 || fmt.indexOf(ph) === -1 ) { + return fmt; + } + //Get replacement values + params = Array.prototype.slice.call(arguments, 1); + val = null; + //Replace placeholders in string with parameters + + //Replace all placeholders at once if single parameter set + if ( params.length === 1 ) { + fmt = fmt.replace(ph, params[0].toString()); + } else { + var idx = 0, + len = params.length, + pos = 0; + while ( ( pos = fmt.indexOf(ph) ) && idx < len ) { + fmt = fmt.substr(0, pos) + params[idx].toString() + fmt.substr(pos + ph.length); + idx++; + } + //Remove any remaining placeholders + fmt = fmt.replace(ph, ''); + } + return fmt; + }, + + /** + * Checks if key(s) exist in an object + * @param object obj Object to check + * @param string|array key Key(s) to check for in object + * @return bool TRUE if key(s) exist in object, FALSE otherwise + */ + in_obj: function(obj, key, all) { + //Validate + if ( !this.is_bool(all) ) { + all = true; + } + if ( this.is_string(key) ) { + key = [key]; + } + var ret = false; + if ( this.is_obj(obj) && this.is_array(key) ) { + var val; + for ( var x = 0; x < key.length; x++ ) { + val = key[x]; + ret = ( this.is_string(val) && ( val in obj ) ) ? true : false; + //Stop processing if conditions have been met + if ( ( !all && ret ) || ( all && !ret ) ) { + break; + } + } + } + return ret; + }, + + /** + * Find common elements of 2 arrays + * @param array arr1 First array + * @param array arr2 Second array + * @return array Elements common to both arrays + */ + arr_intersect: function(arr1, arr2) { + var ret = []; + if ( arr1 === arr2 ) { + return arr2; + } + if ( !$.isArray(arr2) || !arr2.length || !arr1.length ) { + return ret; + } + //Compare elements in arrays + var a1; + var a2; + var val; + if ( arr1.length < arr2.length ) { + a1 = arr1; + a2 = arr2; + } else { + a1 = arr2; + a2 = arr1; + } + + for ( var x = 0; x < a1.length; x++ ) { + //Add mutual elements into intersection array + val = a1[x]; + if ( a2.indexOf(val) !== -1 && ret.indexOf(val) === -1 ) { + ret.push(val); + } + } + + //Return intersection results + return ret; + } + } +}; +var SLB_Base = Class.extend(Base); + +//Init global object +var Core = { + /* Properties */ + + base: true, + context: [], + + /** + * New object initializer + * @var obj + */ + Class: SLB_Base, + + /* Methods */ + + /** + * Setup client + * Set variables, DOM, etc. + */ + setup_client: function() { + /* Quick Hide */ + $('html').addClass(this.util.get_prefix()); + } +}; +var SLB_Core = SLB_Base.extend(Core); + +this.SLB = new SLB_Core(); + +this.SLB.setup_client(); + })(jQuery);} \ No newline at end of file diff --git a/client/js/lib.view.js b/client/js/dev/lib.view.js similarity index 92% rename from client/js/lib.view.js rename to client/js/dev/lib.view.js index 1b9bf5f..020484c 100644 --- a/client/js/lib.view.js +++ b/client/js/dev/lib.view.js @@ -1,4556 +1,4587 @@ -/** - * View (Lightbox) functionality - * @package Simple Lightbox - * @subpackage View - * @author Archetyped - */ - -if ( jQuery ){(function ($) { - -if ( typeof SLB == 'undefined' || !SLB.attach ) { - return false; -} - -/*-** Controller **-*/ - -var View = { - - /* Properties */ - - /** - * Media item properties - * > Item key: Link URI - * > Base properties - * > id: WP Attachment ID - * > source: Source URI - * > title: Media title (generally WP attachment title) - * > desc: Media description (generally WP Attachment content) - * > type: Asset type (attachment, image, etc.) - */ - assets: {}, - - /** - * Component types that can have default instances - * @var array - */ - component_defaults: [], - - /** - * Collection of jQuery.Deferred instances added during loading routine - * @var array - */ - loading: [], - - /* Component Collections */ - - viewers: {}, - items: [], - content_handlers: {}, - groups: {}, - template_tags: {}, - - /** - * Collection/Data type mapping - * > Key: Collection name - * > Value: Data type - * @var object - */ - collections: {}, - - /** - * Temporary component instances - * For use by controller when no component instance is available - * > Key: Component slug - * > Value: Component instance - */ - component_temps: {}, - - /* Options */ - options: { - ui_animate: true, - slideshow_enabled: true, - slideshow_autostart: false, - slideshow_duration: '6' - }, - - /* Methods */ - - /* Init */ - - update_refs: function() { - var c; - var r; - var ref; - for ( var p in this ) { - if ( !this.util.is_func(this[p]) || !( '_refs' in this[p].prototype ) ) { - continue; - } - //Set component - c = this[p]; - if ( !this.util.is_empty(c.prototype._refs) ) { - for ( r in c.prototype._refs ) { - ref = c.prototype._refs[r]; - if ( this.util.is_func(ref) ) { - continue; - } - if ( this.util.is_string(ref) && ref in this ) { - ref = c.prototype._refs[r] = this[ref]; - } - if ( !this.util.is_func(ref) ) { - delete c.prototype_refs[r]; - } - } - } - } - - /* Initialize components */ - this.init_components(); - }, - - /** - * Initialization - */ - init: function(options) { - var t = this; - $.when.apply($, this.loading).always(function() { - //Set options - $.extend(true, t.options, options); - //History - $(window).on('popstate', function(e) { - var state = e.originalEvent.state; - if ( t.util.in_obj(state, ['item', 'viewer']) ) { - var v = t.get_viewer(state.viewer); - v.history_handle(e); - return e.preventDefault(); - } - }); - - /* Set defaults */ - - //Items - t.init_items(); - }); - }, - - init_components: function() { - this.collections = { - 'viewers': this.Viewer, - 'items': this.Content_Item, - 'content_handlers': this.Content_Handler, - 'groups': this.Group, - 'themes': this.Theme, - 'template_tags': this.Template_Tag - }; - - this.component_defaults = [ - this.Viewer, - ]; - - }, - - /* Components */ - - component_make_default: function(type) { - var ret = false; - for ( var x = 0; x < this.component_defaults.length; x++ ) { - if ( type == this.component_defaults[x] ) { - ret = true; - break; - } - } - return ret; - }, - - /** - * Validates component type - * @param function comp Component type to check - * @return bool TRUE if param is valid component, FALSE otherwise - */ - check_component: function(comp) { - //Validate component type - return ( this.util.is_func(comp) && ('_slug' in comp.prototype ) && ( comp.prototype instanceof (this.Component) ) ) ? true : false; - }, - - /** - * Retrieve collection of components of specified type - * @param function type Component type - * @return object|array|null Component collection (NULL if invalid) - */ - get_components: function(type) { - var ret = null; - if ( this.util.is_func(type) ) { - //Determine collection - for ( var coll in this.collections ) { - if ( type == this.collections[coll] && coll in this ) { - ret = this[coll]; - break; - } - } - } - return ret; - }, - - /** - * Retrieve component from specific collection - * @param function type Component type - * @param string id Component ID - * @return object|null Component reference (NULL if invalid) - */ - get_component: function(type, id) { - var ret = null; - //Validate parameters - if ( !this.util.is_func(type) ) { - return ret; - } - //Sanitize id - if ( !this.util.is_string(id) ) { - id = null; - } - - //Get component from collection - var coll = this.get_components(type); - if ( this.util.is_obj(coll) ) { - var tid = ( this.util.is_string(id) ) ? id : this.util.add_prefix('default'); - if ( tid in coll ) { - ret = coll[tid]; - } - } - - //Default: Create default component - if ( this.util.is_empty(ret) ) { - if ( this.util.is_string(id) || this.component_make_default(type) ) { - ret = this.add_component(type, id); - } - } - //Return component - return ret; - }, - - /** - * Create new component instance and save to appropriate collection - * @param function type Component type to create - * @param string id ID of component - * @param object options Component initialization options (Default options used if default component is allowed) - * @return object|null New component (NULL if invalid) - */ - add_component: function(type, id, options) { - //Validate type - if ( !this.util.is_func(type) ) { - return false; - } - //Validate request - if ( this.util.is_empty(id) && !this.component_make_default(type) ) { - return false; - } - //Defaults - var ret = null; - if ( this.util.is_empty(id) ) { - id = this.util.add_prefix('default'); - } - if ( !this.util.is_obj(options) ) { - options = {}; - } - //Check if specialized method exists for component type - var m = ( 'component' != type.prototype._slug ) ? 'add_' + type.prototype._slug : null; - if ( !this.util.is_empty(m) && ( m in this ) && this.util.is_func(this[m]) ) { - ret = this[m](id, options); - } - //Default process - else { - ret = new type(id, options); - } - - //Add new component to collection - if ( this.util.is_type(ret, type) ) { - //Get collection - var coll = this.get_components(type); - //Add to collection - switch ( $.type(coll) ) { - case 'object' : - coll[id] = ret; - break; - case 'array' : - coll.push(ret); - break; - } - } else { - ret = null; - } - //Return new component - return ret; - }, - - /** - * Create new temporary component instance - * @param function type Component type - * @return New temporary instance - */ - add_component_temp: function(type) { - var ret = null; - if ( this.check_component(type) ) { - //Create new instance - ret = new type(''); - //Save to collection - this.component_temps[ret._slug] = ret; - } - return ret; - }, - - /** - * Retrieve temporary component instance - * Creates new temp component instance for type if not previously created - * @param function type Component type to retrieve temp instance for - * @return obj Temporary component instance - */ - get_component_temp: function(type) { - return ( this.has_component_temp(type) ) ? this.component_temps[type.prototype._slug] : this.add_component_temp(type); - }, - - /** - * Check if temporary component instance exists - * @param function type Component type to check for - * @return bool TRUE if temp instance exists, FALSE otherwise - */ - has_component_temp: function(type) { - return ( this.check_component(type) && ( type.prototype._slug in this.component_temps ) ) ? true : false; - }, - - /* Properties */ - - /** - * Retrieve specified options - * @param array opts Array of option names - * @return object Specified options (Default: empty object) - */ - get_options: function(opts) { - var ret = {}; - //Validate - if ( this.util.is_string(opts) ) { - opts = [opts]; - } - if ( !this.util.is_array(opts) ) { - return ret; - } - //Get specified options - for ( var x = 0; x < opts.length; x++ ) { - //Skip if option not set - if ( !( opts[x] in this.options ) ) { - continue; - } - ret[ opts[x] ] = this.options[ opts[x] ]; - } - return ret; - }, - - /** - * Retrieve option - * @uses View.options - * @param string opt Option to retrieve - * @param mixed def (optional) Default value if option does not exist (Default: NULL) - * @return mixed Option value - */ - get_option: function(opt, def) { - var ret = this.get_options(opt); - if ( this.util.is_obj(ret) && ( opt in ret ) ) { - ret = ret[opt]; - } else { - ret = ( this.util.is_set(def) ) ? def : null; - } - return ret; - }, - - /* Viewers */ - - /** - * Add viewer instance to collection - * @param string id Viewer ID - * @param obj options Viewer options - */ - add_viewer: function(id, options) { - //Validate - if ( !this.util.is_string(id) ) { - return false; - } - if ( !this.util.is_obj(options, false) ) { - options = {}; - } - //Create viewer - var v = new this.Viewer(id, options); - //Add to collection - this.viewers[v.get_id()] = v; - //Return viewer - return v; - }, - - /** - * Retrieve all viewer instances - * @return obj Viewer instances - */ - get_viewers: function() { - return this.viewers; - }, - - /** - * Check if viewer exists - * @param string v Viewer ID - * @return bool TRUE if viewer exists, FALSE otherwise - */ - has_viewer: function(v) { - return ( this.util.is_string(v) && v in this.get_viewers() ) ? true : false; - }, - - /** - * Retrieve Viewer instance - * Default viewer retrieved if specified viewer does not exist - * > Default viewer created if necessary - * @param string v Viewer ID to retrieve - * @return Viewer Viewer instance - */ - get_viewer: function(v) { - //Retrieve default viewer if specified viewer not set - if ( !this.has_viewer(v) ) { - v = this.util.add_prefix('default'); - //Create default viewer if necessary - if ( !this.has_viewer(v) ) { - this.add_viewer(v); - } - } - return this.get_viewers()[v]; - }, - - /* Items */ - - /** - * Set event handlers - */ - init_items: function() { - //Define handler - var t = this; - var handler = function() { - var ret = t.show_item(this); - if ( !t.util.is_bool(ret) ) { - ret = true; - } - return !ret; - }; - - //Get activated links - var sel = this.util.format('a[href][%s="%s"]', this.util.get_attribute('active'), 1); - //Add event handler - $(document).on('click', sel, handler); - }, - - get_items: function() { - return this.get_components(this.Content_Item); - }, - - /** - * Retrieve specific Content_Item instance - * @param mixed Item reference - * > Content_Item: Item instance (returned immediately) - * > DOM element: DOM element to get item for - * > int: Index of cached item - * @return Content_Item Item instance for DOM node - */ - get_item: function(ref) { - //Evaluate reference type - - //Content Item instance - if ( this.util.is_type(ref, this.Content_Item) ) { - return ref; - } - //Retrieve item instance - var item = null; - - //DOM element - if ( this.util.in_obj(ref, 'nodeType') ) { - //Check if item instance attached to element - var key = this.get_component_temp(this.Content_Item).get_data_key(); - item = $(ref).data(key); - } - //Cached item (index) - else if ( this.util.is_int(ref, false) ) { - var items = this.get_items(); - if ( items.length > ref ) { - item = items[ref]; - } - } - //Create default item instance - if ( !this.util.is_type(item, this.Content_Item) ) { - item = this.add_item(ref); - } - return item; - }, - - /** - * Create new item instance - * @param obj el DOM element representing item - * @return Content_Item New item instance - */ - add_item: function(el) { - var item = new this.Content_Item(el); - return item; - }, - - /** - * Display item in viewer - * @param obj el DOM element representing item - */ - show_item: function(el) { - var ret = this.get_item(el).show(); - return ret; - }, - - /** - * Cache item instance - * @uses this.items to store cached items - * @param Content_Item item Item to cache - * @return int Index of item in cache - */ - save_item: function(item) { - var ret = -1; - if ( !this.util.is_type(item, this.Content_Item) ) { - return ret; - } - var prop = 'items'; - var items = this.get_items(); - //Check if item exists in collection - ret = $.inArray(item, items); - //Cache item - if ( -1 == ret ) { - ret = items.push(item) - 1; - } - //Return item index in cache - return ret; - }, - - /* Content Handler */ - - get_content_handlers: function() { - return this.get_components(this.Content_Handler); - }, - - /** - * Find matching content handler for item - * @param Content_Item|string item Item to find handler for (or ID of Handler) - * @return Content_Handler|null Matching content handler (NULL if no matching handler found) - */ - get_content_handler: function(item) { - //Determine handler to retrieve - var type = ( this.util.is_type(item, this.Content_Item) ) ? item.get_attribute('type', '') : item.toString(); - //Retrieve handler - var types = this.get_content_handlers(); - return ( type in types ) ? types[type] : null; - }, - - /** - * Add/Update Content Handler - * @param string id Handler ID - * @param obj attr Handler attributes - * @return obj|bool Handler instance (FALSE on failure) - */ - extend_content_handler: function(id, attr) { - var hdl = false; - if ( !this.util.is_string(id) || !this.util.is_obj(attr) ) { - return hdl; - } - hdl = this.get_content_handler(id); - //Add new content handler - if ( null == hdl ) { - var hdls = this.get_content_handlers(); - hdls[id] = hdl = new this.Content_Handler(id, attr); - } - //Update existing handler - else { - hdl.set_attributes(attr); - } - //Load styles - if ( this.util.in_obj(attr, 'styles') ) { - this.load_styles(attr.styles); - } - return hdl; - }, - - /* Group */ - - /** - * Add new group - * @param string g Group ID - * > If group with same ID already set, new group replaces existing one - * @param object attrs (optional) Group attributes - */ - add_group: function(g, attrs) { - //Create new group - g = new this.Group(g, attrs); - //Add group to collection - if ( this.util.is_string(g.get_id()) ) { - this.groups[g.get_id()] = g; - } - }, - - /** - * Retrieve groups - * @uses groups property - * @return object Registered groups - */ - get_groups: function() { - return this.groups; - }, - - /** - * Retrieve specified group - * @param string g Group ID - * @return object|null Group instance (NULL if group does not exist) - */ - get_group: function(g) { - if ( this.util.is_string(g) ) { - if ( !this.has_group(g) ) { - //Add new group (if necessary) - this.add_group(g); - } - //Retrieve group - g = this.get_groups()[g]; - } - return ( this.util.is_type(g, this.Group) ) ? g : null; - }, - - /** - * Checks if group is registered - * @uses get_groups() to retrieve registered groups - * @return bool TRUE if group exists, FALSE otherwise - */ - has_group: function(g) { - return ( this.util.is_string(g) && ( g in this.get_groups() ) ) ? true : false; - }, - - /* Theme */ - - /** - * Add/Update theme - * @param string name Theme name - * @param obj attr Theme options - * > Multiple attribute parameters are merged - * @return obj Theme model - */ - extend_theme: function(id, attr) { - var t = this; - //Validate - if ( !this.util.is_string(id) ) { - return false; - } - var dfr = $.Deferred(); - this.loading.push(dfr); - - //Get model if it already exists - var model = this.get_theme_model(id); - - //Create default attributes for new theme - if ( this.util.is_empty(model) ) { - //Default - var model = {'parent': null, 'id': id}; - //Save theme model - this.Theme.prototype._models[id] = model; - } - - //Add custom attributes - if ( this.util.is_obj(attr) ) { - //Sanitize - if ( 'id' in attr ) { - delete(attr['id']); - } - $.extend(model, attr); - } - - //Load styles - if ( this.util.in_obj(attr, 'styles') ) { - this.load_styles(attr.styles); - } - - //Link parent model - if ( this.util.is_string(model.parent) ) { - model.parent = this.get_theme_model(model.parent); - } - - //Complete loading when all components loaded - dfr.resolve(); - return model; - }, - - /** - * Retrieve theme models - * @return obj Theme models - */ - get_theme_models: function() { - //Retrieve matching theme model - return this.Theme.prototype._models; - }, - - /** - * Retrieve theme model - * @param string id Theme to retrieve - * @return obj Theme model (Default: empty object) - */ - get_theme_model: function(id) { - var ms = this.get_theme_models(); - return ( this.util.in_obj(ms, id) ) ? ms[id] : {}; - }, - - /** - * Add/Update Template Tag Handler - * @param string id Handler ID - * @param obj attr Handler attributes - * @return obj|bool Handler instance (FALSE on failure) - */ - extend_template_tag_handler: function(id, attr) { - var hdl = false; - if ( !this.util.is_string(id) || !this.util.is_obj(attr) ) { - return hdl; - } - var hdls = this.get_template_tag_handlers(); - //Add new content handler - if ( !this.util.in_obj(hdls, id) ) { - hdls[id] = hdl = new this.Template_Tag_Handler(id, attr); - } - //Update existing handler - else { - hdl = hdls[id]; - hdl.set_attributes(attr); - } - //Load styles - if ( this.util.in_obj(attr, 'styles') ) { - this.load_styles(attr.styles); - } - return hdl; - }, - - /** - * Retrieve Template Tag Handler collection - * @return obj Template_Tag_Handler objects - */ - get_template_tag_handlers: function() { - return this.Template_Tag.prototype.handlers; - }, - - /** - * Retrieve template tag handler - * @param string id ID of tag handler to retrieve - * @return Template_Tag_Handler Tag Handler instance (new instance for invalid ID) - */ - get_template_tag_handler: function(id) { - var handlers = this.get_template_tag_handlers(); - //Retrieve existing handler - if ( this.util.is_string(id) && ( id in handlers ) ) { - return handlers[id]; - } - //Default: Return empty handler - return new this.Template_Tag_Handler(id, {}); - }, - - /** - * Load styles - * @param array styles Styles to load - */ - load_styles: function(styles) { - if ( this.util.is_array(styles) ) { - var out = ''; - $.each(styles, function(i, style) { - out += ''; - }); - $('head').append(out); - } - } -}; - -/* Components */ -var Component = { - /*-** Properties **-*/ - - /* Internal/Configuration */ - - /** - * Base name of component type - * @var string - */ - _slug: 'component', - - /** - * Valid component references for current component - * > Key (string): Property name that stores reference - * > Value (function): Data type of component - * @var object - */ - _refs: {}, - - /** - * Components that may contain current object - * Used for retrieving data from a parent object - * Example: An Item may be contained by a Group - * > Value (strong): Property name of container component - * @var array - */ - _containers: [], - - /** - * Whether DOM element and component are connected in 1:1 relationship - * Some components will be assigned to different DOM elements depending on usage - * @var bool - */ - _reciprocal: false, - - /** - * DOM Element tied to component - * @var DOM Element - */ - _dom: null, - - /** - * Default attributes - * @var object - */ - _attr_default: {}, - - /** - * Attributes passed to constructor - * @var obj - */ - _attr_init: null, - - /** - * Attributes to retrieve from parent (controller) - * @var array - */ - _attr_parent: [], - - /** - * Defines how parent properties should be remapped to component properties - * @var object - */ - _attr_map: {}, - - /** - * Event handlers - * @var object - * > Key: string Event type - * > Value: array Handlers - */ - _events: null, - - /** - * Status management - * @var object - * > Key: Status ID - * > Value: Status value - */ - _status: null, - - /* Public */ - - attributes: false, - - /** - * Component ID - * @var string - */ - id: '', - - /* Init */ - - _c: function(id, attributes) { - //Set ID - this.set_id(id); - //Save init attributes - this._attr_init = attributes; - this.register_hooks(); - }, - - _set_parent: function() { - this._parent = View; - this.util._parent = this; - }, - - /** - * Register hooks on init - * Placeholder method to be overridden by child classes - */ - register_hooks: function() {}, - - /* Methods */ - - /* Properties */ - - /** - * Retrieve status - * @param string id Status to retrieve - * @param bool raw (optional) Retrieve raw value (Default: FALSE) - * @return mixed Status value (Default: bool) - */ - get_status: function(id, raw) { - var ret = false; - if ( this.util.in_obj(this._status, id) ) { - ret = ( !!raw ) ? this._status[id] : !!this._status[id]; - } - return ret; - }, - - /** - * Set status - * @param string id Status to retrieve - * @param mixed val Status value (Default: TRUE) - * @return mixed Status value (Default: bool) - */ - set_status: function(id, val) { - //Validate - if ( this.util.is_string(id) ) { - if ( !this.util.is_set(val) ) { - val = true; - } - //Initialize property - if ( !this.util.is_obj(this._status, false) ) { - this._status = {}; - } - //Set status - this._status[id] = val; - } else if ( !this.util.is_set(val) ) { - val = false; - } - return val; - }, - - /** - * Retrieve instance ID - * @uses id as ID base - * @uses _slug to add namespace (if necessary) - * @param bool ns (optional) Whether or not to namespace ID (Default: FALSE) - * @return string Instance ID - */ - get_id: function(ns) { - //Validate - if ( !this.check_id() ) { - this.id = ''; - } - var id = this.id; - //Namespace ID - if ( this.util.is_bool(ns) && ns ) { - id = this.add_ns(id); - } - - return id; - }, - - /** - * Set instance ID - * @param string id Unique ID - */ - set_id: function(id) { - this.id = ( this.check_id(id) ) ? id : ''; - }, - - /** - * Validate ID value - * @param string id (optional) ID value (Default: Component ID) - * @param bool nonempty (optional) TRUE if it should also check for empty strings, FALSE otherwise (Default: FALSE) - * @return bool TRUE if ID is valid, FALSE otherwise - */ - check_id: function(id, nonempty) { - //Validate - if ( arguments.length == 1 && this.util.is_bool(arguments[0]) ) { - nonempty = arguments[0]; - id = null; - } - if ( this.util.is_empty(id) ) { - id = this.id; - } - if ( !this.util.is_bool(nonempty) ) { - nonempty = false; - } - return ( this.util.is_string(id, nonempty) ) ? true : false; - }, - - /** - * Get namespace - * @uses _slug for namespace segment - * @uses Util.add_prefix() to prefix slug - * @return string Component namespace - */ - get_ns: function() { - return this.util.add_prefix(this._slug); - }, - - /** - * Add namespace to value - * @param string val Value to namespace - * @return string Namespaced value (Empty string if invalid value provided) - */ - add_ns: function(val) { - return ( this.util.is_string(val) ) ? this.get_ns() + '_' + val : ''; - }, - - /* Components */ - - /** - * Retrieve component containers - * @uses _container property - * @return array Component containers - */ - get_containers: function() { - //Sanitize property - if ( !this.util.is_array(this._containers) ) { - this._containers = []; - } - //Return value - return this._containers; - }, - - /** - * Check if current object has potential container objects - * @return bool TRUE if containers exist, FALSE otherwise - */ - has_containers: function() { - return ( this.get_containers().length > 0 ); - }, - - /** - * Check if reference exists in object - * @param string ref Reference ID - * @return bool TRUE if reference exists, FALSE otherwise - */ - has_reference: function(ref) { - return ( this.util.is_string(ref) && ( ref in this ) && ( ref in this.get_references() ) ) ? true : false; - }, - - /** - * Retrieve object references - * @uses _refs - * @return obj References object - - */ - get_references: function() { - return this._refs; - }, - - /** - * Retrieve reference data type - * @param string ref Reference ID - * @return function Reference data type (NULL if invalid) - */ - get_reference: function(ref) { - return ( this.has_reference(ref) ) ? this._refs[ref] : null; - }, - - /** - * Checks if component is valid - * @param obj|string Component instance or ID - * > If ID is specified then it will check for component on current instance - * @param function|string ctype Component type - * > If component is an object, then ctype is required - * > If component is string ID, then ctype is optional (Default: reference type) - * > If ctype is a function, then it is compared to component directly - * > If ctype is a string, then the component reference type is retrieved - * @uses get_reference() - * @return bool TRUE if component is valid, FALSE otherwise - */ - check_component: function(comp, ctype) { - //Validate - if ( this.util.is_empty(comp) - || ( this.util.is_obj(comp) && !this.util.is_func(ctype) ) - || ( this.util.is_string(comp) && !this.has_reference(comp) ) - || ( this.util.is_empty(ctype) && !this.util.is_string(comp) ) - || ( !this.util.is_obj(comp) && !this.util.is_string(comp) ) - ) { - return false; - } - //Get component type - if ( !this.util.is_func(ctype) ) { - //Component is a string ID - ctype = this.get_reference(comp); - } - //Get component instance - if ( this.util.is_string(comp) ) { - comp = this.get_component(comp, false); - } - return this.util.is_type(comp, ctype); - }, - - /** - * Retrieve component reference from current object - * > Procedure: - * > Check if property already set - * > Check attributes - * > Check container object(s) - * > Check parent object (controller) - * @uses _containers to check potential container components for references - * @param string cname Component name - * @param bool check_attr (optional) Whether or not to check instance attributes for component (Default: TRUE) - * @param bool get_default (optional) Whether or not to retrieve default object from controller if none exists in current instance (Default: TRUE) - * @param bool recursive (optional) Whether or not to check containers for specified component reference (Default: TRUE) - * @return object|null Component reference (NULL if no component found) - */ - get_component: function(cname, check_attr, get_default, recursive) { - var c = null; - //Validate request - if ( !this.util.is_string(cname) || !( cname in this ) || !this.has_reference(cname) ) { - return c; - } - - //Normalize parameters - if ( !this.util.is_bool(check_attr) ) { - check_attr = true; - } - if ( !this.util.is_bool(get_default) ) { - get_default = true; - } - if ( !this.util.is_bool(recursive) ) { - recursive = true; - } - var ctype = this._refs[cname]; - //Phase 1: Check if component reference previously set - if ( this.util.is_type(this[cname], ctype) ) { - return this[cname]; - } - //If reference not set, iterate through component hierarchy until reference is found - c = this[cname] = null; - - //Phase 2: Check attributes - if ( check_attr ) { - c = this.get_attribute(cname); - //Save object-specific component reference - if ( !this.util.is_empty(c) ) { - c = this.set_component(cname, c); - } - } - - //Phase 3: Check Container(s) - if ( recursive && this.util.is_empty(c) && this.has_containers() ) { - var containers = this.get_containers(); - var con = null; - for ( var i = 0; i < containers.length; i++ ) { - con = containers[i]; - //Validate container - if ( con == cname ) { - continue; - } - //Retrieve container - con = this.get_component(con, true, false); - if ( this.util.is_empty(con) ) { - continue; - } - //Attempt to retrieve component from container - c = con.get_component(cname); - //Stop iterating if valid component found - if ( !this.util.is_empty(c) ) { - break; - } - } - } - - //Phase 4: From controller (optional) - if ( get_default && this.util.is_empty(c) ) { - c = this.get_parent().get_component(ctype); - } - return c; - }, - - /** - * Sets component reference on current object - * > Component property reset (set to NULL) if invalid component supplied - * @param string name Name of property to set component on object - * @param string|object ref Component or Component ID (to be retrieved from controller) - * @param function validate (optional) Additional validation to be performed (Must return bool: TRUE (Valid)/FALSE (Invalid)) - * @return object Component (NULL if invalid) - */ - set_component: function(name, ref, validate) { - var clear = null; - //Make sure component property exists - if ( !this.has_reference(name) ) { - return clear; - } - //Normalize reference - if ( this.util.is_empty(ref) ) { - ref = clear; - } - var ctype = this.get_reference(name); - - //Get component from controller if ID supplied - if ( this.util.is_string(ref) ) { - ref = this.get_parent().get_component(ctype, ref); - } - - if ( !this.util.is_type(ref, ctype) ) { - ref = clear; - } - - //Additional validation - if ( !this.util.is_empty(ref) && this.util.is_func(validate) && !validate.call(this, ref) ) { - ref = clear; - } - //Set (or clear) component reference - this[name] = ref; - //Return value for confirmation - return this[name]; - }, - - /* Attributes */ - - /** - * Initializes attributes - */ - init_attributes: function(force) { - if ( !this.util.is_bool(force) ) { - force = false; - } - if ( force || !this.util.is_obj(this.attributes) ) { - this.attributes = {}; - //Build attribute groups - var attrs = [{}]; - attrs.push(this.init_default_attributes()); - if ( this.dom_has() ) { - attrs.push(this.get_dom_attributes()); - } - if ( this.util.is_obj(this._attr_init) ) { - attrs.push(this._attr_init); - } - //Merge attributes - this.attributes = $.extend.apply(null, attrs); - } - }, - - /** - * Generate default attributes for component - * @uses _attr_parent to determine options to retrieve from controller - * @uses View.get_options() to get values from controller - * @uses _attr_map to Remap controller attributes to instance attributes - * @uses _attr_default to Store default attributes - */ - init_default_attributes: function() { - //Get parent options - var opts = this.get_parent().get_options(this._attr_parent); - if ( this.util.is_obj(opts) ) { - //Remap - for ( var opt in this._attr_map ) { - if ( opt in opts ) { - //Move value to new property - opts[this._attr_map[opt]] = opts[opt]; - //Delete old property - delete opts[opt]; - } - } - //Merge with default attributes - $.extend(true, this._attr_default, opts); - } - return this._attr_default; - }, - - /** - * Retrieve DOM attributes - */ - get_dom_attributes: function() { - var attrs = {}; - var el = this.dom_get(); - if ( el.length ) { - //Get attributes from element - var opts = $(el).get(0).attributes; - if ( this.util.is_obj(opts) ) { - var attr_prefix = this.util.get_attribute(); - $.each(opts, function(idx, opt) { - if ( opt.name.indexOf( attr_prefix ) == -1 ) { - return true; - } - //Process custom attributes - //Strip prefix - var key = opt.name.substr(attr_prefix.length + 1); - attrs[key] = opt.value; - }); - } - } - return attrs; - }, - - /** - * Retrieve all instance attributes - * @uses parse_attributes() to initialize attributes (if necessary) - * @uses attributes - * @return obj Attributes - */ - get_attributes: function() { - //Initilize attributes - this.init_attributes(); - //Return attributes - return this.attributes; - }, - - /** - * Retrieve value of specified attribute for value - * @param string key Attribute to retrieve - * @param mixed def (optional) Default value if attribute is not set - * @param bool enforce_type (optional) Whether data type should match default value (Default: TRUE) - * > If possible, attribute value will be converted to match default data type - * > If attribute value cannot match default data type, default value will be used - * @return mixed Attribute value (NULL if attribute is not set) - */ - get_attribute: function(key, def, enforce_type) { - //Validate - if ( !this.util.is_set(def) ) { - def = null; - } - if ( !this.util.is_string(key) ) { - return def; - } - if ( !this.util.is_bool(enforce_type) ) { - enforce_type = true; - } - //Get attribute value - var ret = ( this.has_attribute(key) ) ? this.get_attributes()[key] : def; - //Validate type - if ( enforce_type && ret !== def && null !== def && !this.util.is_type(ret, $.type(def), false) ) { - //Convert type - //Scalar default - if ( this.util.is_scalar(def, false) ) { - //Non-scalar attribute - if ( !this.util.is_scalar(ret, false) ) { - ret = def; - } else if ( this.util.is_string(def, false) ) { - ret = ret.toString(); - } else if ( this.util.is_num(def, false) && !this.util.is_num(ret, false) ) { - ret = ( this.util.is_int(def, false) ) ? parseInt(ret) : parseFloat(ret); - if ( !this.util.is_num(ret, false) ) { - ret = def; - } - } else if ( this.util.is_bool(def, false) ) { - ret = ( this.util.is_string(ret) || ( this.util.is_num(ret) ) ); - } else { - ret = def; - } - } - //Non-scalar default - else { - ret = def; - } - } - return ret; - }, - - /** - * Call attribute as method - * @param string attr Attribute to call - * @param arguments (optional) Additional arguments to pass to method - */ - call_attribute: function(attr, args) { - attr = this.get_attribute(attr); - if ( this.util.is_func(attr) ) { - //Get arguments - var args = Array.prototype.slice.call(arguments, 1); - //Pass arguments to user-defined method - attr = attr.apply(this, args); - } - return attr; - }, - - /** - * Check if attribute exists - * @param string key Attribute name - * @return bool TRUE if exists, FALSE otherwise - */ - has_attribute: function(key) { - return ( key in this.get_attributes() ); - }, - - /** - * Set component attributes - * @param obj attributes Attributes to set - * @param bool full (optional) Whether to fully replace or merge component's attributes with new values (Default: Merge) - */ - set_attributes: function(attributes, full) { - if ( !this.util.is_bool(full) ) { - full = false; - } - - //Initialize attributes - this.init_attributes(full); - - //Merge new/existing attributes - if ( this.util.is_obj(attributes) ) { - $.extend(this.attributes, attributes); - } - }, - - /** - * Set value for a component attribute - * @uses get_attributes() to retrieve attributes - * @param string key Attribute to set - * @param mixed val Attribute value - */ - set_attribute: function(key, val) { - if ( this.util.is_string(key) && this.util.is_set(val) ) { - this.get_attributes()[key] = val; - } - return val; - }, - - /* DOM */ - - /** - * Generate selector for retrieving child element - * @param string element Class name of child element - * @return string Element selector - */ - dom_get_selector: function(element) { - return ( this.util.is_string(element) ) ? '.' + this.add_ns(element) : ''; - }, - - dom_get_attribute: function() { - return this.util.get_attribute(this._slug); - }, - - /** - * Set reference of instance on DOM element - * @uses _reciprocal to determine if DOM element should also be attached to instance - * @param string|obj (jQuery) el DOM element to attach instance to - * @return jQuery DOM element set - */ - dom_set: function(el) { - el = $(el); - //Save instance to DOM object - el.data(this.get_data_key(), this); - //Save DOM object to instance - if ( this._reciprocal ) { - this._dom = el; - } - return el; - }, - - /** - * Retrieve attached DOM element - * @uses _dom to retrieve attached DOM element - * @uses dom_put() to insert child element - * @param string element Child element to retrieve - * @param bool put (optional) Whether to insert element if it does not exist (Default: FALSE) - * @param obj options (optional) Options for creating new object - * @return obj jQuery DOM element - */ - dom_get: function(element, put, options) { - //Init Component DOM - if ( !this.get_status('dom_init') ) { - this.set_status('dom_init'); - this.dom_init(); - } - //Check for main DOM element - var ret = this._dom; - if ( !!ret && this.util.is_string(element) ) { - var ch = $(ret).find( this.dom_get_selector(element) ); - //Check for child element - if ( ch.length ) { - ret = ch; - } else if ( this.util.is_bool(put) && put ) { - //Insert child element - ret = this.dom_put(element, options); - } - } - return $(ret); - }, - - /** - * Initialize DOM element - * To be overridden by child classes - */ - dom_init: function() {}, - - /** - * Wrap output in DOM element - * Wrapper element created and added to main DOM element if not yet created - * @param string element ID for DOM element (Used as class name for wrapper) - * @param string|jQuery|obj content Content to add to DOM (Object contains element properties) - * > tag : Element tag name - * > content : Element content - * @return jQuery Inserted element(s) - */ - dom_put: function(element, content) { - var r = null; - //Stop processing if main DOM element not set or element is not valid - if ( !this.dom_has() || !this.util.is_string(element) ) { - return $(r); - } - //Setup options - var strip = ['tag', 'content', 'put_success']; - var options = { - 'tag': 'div', - 'content': '', - 'class': this.add_ns(element) - } - //Setup content - if ( !this.util.is_empty(content) ) { - if ( this.util.is_type(content, jQuery, false) || this.util.is_string(content, false) ) { - options.content = content; - } - else if ( this.util.is_obj(content, false) ) { - $.extend(options, content); - } - } - var attrs = $.extend({}, options); - for ( var x = 0; x < strip.length; x++ ) { - delete attrs[strip[x]]; - } - //Retrieve existing element - var d = this.dom_get(); - r = $(this.dom_get_selector(element), d); - //Create element (if necessary) - if ( !r.length ) { - r = $(this.util.format('<%s />', options.tag), attrs).appendTo(d); - if ( r.length && this.util.is_method(options, 'put_success') ) { - options['put_success'].call(r, r); - } - } - //Set content - $(r).append(options.content); - return $(r); - }, - - /** - * Check if DOM element is set for instance - * DOM is initialized before evaluation - * @return bool TRUE if DOM element set, FALSE otherwise - */ - dom_has: function() { - return ( !!this.dom_get().length ); - }, - - /* Data */ - - /** - * Retrieve key used to store data in DOM element - * @return string Data key - */ - get_data_key: function() { - return this.get_ns(); - }, - - /* Events */ - - /** - * Register event handler for custom event - * Structure - * > Events (obj) - * > Event-Name (array) - * > Handlers (functions) - * @param mixed event Custom event to register handler for - * > string: Standard event handler - * > array: Multiple events to register single handler on - * > object: Map of events/handlers - * @param function fn Event handler - * @param obj options Handler registration options - * > clear (bool) Clear existing event handlers before setting current handler (Default: FALSE) - * @return obj Component instance (allows chaining) - */ - on: function(event, fn, options) { - //Handle request types - if ( !this.util.is_string(event) || !this.util.is_func(fn) ) { - var t = this; - var args = Array.prototype.slice.call(arguments, 1); - if ( this.util.is_array(event) ) { - //Events array - $.each(event, function(idx, val) { - t.on.apply(t, [val].concat(args)); - }); - } else if ( this.util.is_obj(event) ) { - //Events map - $.each(event, function(ev, hdl) { - t.on.apply(t, [ev, hdl].concat(args)); - }); - } - return this; - } - - //Options - - //Default options - var options_std = { - clear: false - }; - if ( !this.util.is_obj(options, false) ) { - //Reset options - options = {}; - } - //Build options - options = $.extend({}, options_std, options); - //Initialize events bucket - if ( !this.util.is_obj(this._events, false) ) { - this._events = {}; - } - //Setup event - var es = this._events; - if ( !( event in es ) || !this.util.is_obj(es[event], false) || !!options.clear ) { - es[event] = []; - } - //Add event handler - es[event].push(fn); - return this; - }, - - /** - * Trigger custom event - * Event handlers are executed in the context of the current component instance - * Event handlers are passed parameters - * > ev (obj) Event object - * > type (string) Event name - * > data (mixed) Data to pass to handlers (if supplied) - * > component (obj) Current component instance - * @param string event Custom event to trigger - * @param mixed data (optional) Data to pass to event handlers - * @return jQuery.Promise Promise that is resolved once event handlers are resolved - */ - trigger: function(event, data) { - var dfr = $.Deferred(); - var dfrs = []; - var t = this; - //Handle array of events - if ( this.util.is_array(event) ) { - $.each(event, function(idx, val) { - //Collect promises from triggered events - dfrs.push( t.trigger(val, data) ); - }); - //Resolve trigger when all events have been resolved - $.when.apply(t, dfrs).done(function() { - dfr.resolve(); - }); - return dfr.promise(); - } - //Validate - if ( !this.util.is_string(event) || !( event in this._events ) ) { - dfr.resolve(); - return dfr.promise(); - } - //Create event object - var ev = { 'type': event, 'data': null }; - //Add data to event object - if ( this.util.is_set(data) ) { - ev.data = data; - } - //Fire handlers for event - $.each(this._events[event], function(idx, fn) { - //Call handler (`this` set to current instance) - //Collect promises from event handlers - dfrs.push( fn.call(t, ev, t) ); - }); - //Resolve trigger when all handlers have been resolved - $.when.apply(this, dfrs).done(function() { - dfr.resolve(); - }); - return dfr.promise(); - } -}; - -View.Component = Component = SLB.Class.extend(Component); - -/** - * Content viewer - * @param obj options Init options - */ -var Viewer = { - - /* Configuration */ - - _slug: 'viewer', - - _refs: { - item: 'Content_Item', - theme: 'Theme' - }, - - _reciprocal: true, - - _attr_default: { - loop: true, - animate: true, - autofit: true, - overlay_enabled: true, - overlay_opacity: '0.8', - container: null, - slideshow_enabled: true, - slideshow_autostart: true, - slideshow_duration: 2, - slideshow_active: false, - slideshow_timer: null, - labels: { - close: 'close', - nav_prev: '« prev', - nav_next: 'next »', - slideshow_start: 'start slideshow', - slideshow_stop: 'stop slideshow', - group_status: 'Image %current% of %total%', - loading: 'loading' - } - }, - - _attr_parent: [ - 'theme', - 'group_loop', - 'ui_autofit', 'ui_animate', 'ui_overlay_opacity', 'ui_labels', - 'slideshow_enabled', 'slideshow_autostart', 'slideshow_duration'], - - _attr_map: { - 'group_loop': 'loop', - 'ui_autofit': 'autofit', - 'ui_animate': 'animate', - 'ui_overlay_opacity': 'overlay_opacity', - 'ui_labels': 'labels' - }, - - /* References */ - - /** - * Item currently loaded in viewer - * @var object Content_Item - */ - item: null, - - /** - * Queued item to be loaded once viewer is available - * @var object Content_Item - */ - item_queued: null, - - /** - * Theme used by viewer - * @var object Theme - */ - theme: null, - - /* Properties */ - - item_working: null, - - active: false, - init: false, - open: false, - loading: false, - - /* Methods */ - - /* Init */ - - register_hooks: function() { - var t = this; - this - .on(['item-prev', 'item-next'], function() { - t.trigger('item-change'); - }) - .on(['close', 'item-change'], function() { - t.unlock(); - }); - }, - - /* References */ - - /** - * Set item reference - * Validates item before setting - * @param obj item Content_Item instance - * @return bool TRUE if valid item set, FALSE otherwise - */ - set_item: function(item) { - //Clear existing item - this.clear_item(false); - var i = this.set_component('item', item, function(item) { - return ( item.has_type() ); - }); - return ( !this.util.is_empty(i) ); - }, - - clear_item: function(full) { - //Validate - if ( !this.util.is_bool(full) ) { - full = true; - } - var item = this.get_item(); - if ( !!item ) { - item.reset(); - } - if ( full ) { - this.set_item(false); - } - }, - - /** - * Retrieve item instance current attached to viewer - * @return Content_Item|NULL Current item instance - */ - get_item: function() { - return this.get_component('item', true, false); - }, - - /** - * Retrieve theme reference - * @return object Theme reference - */ - get_theme: function() { - //Get saved theme - var ret = this.get_component('theme', false, false, false); - if ( this.util.is_empty(ret) ) { - //Theme needs to be initialized - ret = this.set_component('theme', new View.Theme(this)); - } - return ret; - }, - - /** - * Set viewer's theme - * @param object theme Theme object - */ - set_theme: function(theme) { - this.set_component('theme', theme); - }, - - /* Properties */ - - /** - * Lock the viewer - * Indicates that item is currently being processed - * @return jQuery.Deferred Resolved when item processing is complete - */ - lock: function() { - return this.set_status('item_working', $.Deferred()); - }, - - /** - * Retrieve lock - * @param bool simple (optional) Whether to return a simple status of the locked status (Default: FALSE) - * @param bool full (optional) Whether to return Deferred (TRUE) or Promise (FALSE) object (Default: FALSE) - * @return jQuery.Promise Resolved when item processing is complete - */ - get_lock: function(simple, full) { - //Validate - if ( !this.util.is_bool(simple) ) { - simple = false; - } - if ( !this.util.is_bool(full) ) { - full = false; - } - var s = 'item_working'; - //Simple status - if ( simple ) { - return this.get_status(s); - } - //Full value - var r = this.get_status(s, true); - if ( !this.util.is_promise(r) ) { - //Create default - r = this.lock(); - } - return ( full ) ? r : r.promise(); - }, - - is_locked: function() { - return this.get_lock(true); - }, - - /** - * Unlock the viewer - * Any callbacks registered for this action will be executed - * @return jQuery.Deferred Resolved instance - */ - unlock: function() { - return this.get_lock(false, true).resolve(); - }, - - /** - * Set Viewer active status - * @param bool mode (optional) Activate or deactivate status (Default: TRUE) - * @return bool Active status - */ - set_active: function(mode) { - if ( !this.util.is_bool(mode) ) { - mode = true; - } - return this.set_status('active', mode); - }, - - /** - * Check Viewer active status - * @return bool Active status - */ - is_active: function() { - return this.get_status('active'); - }, - - /** - * Set loading mode - * @param bool mode (optional) Set (TRUE) or unset (FALSE) loading mode (Default: TRUE) - * @return jQuery.Promise Promise that resolves when loading mode is set - */ - set_loading: function(mode) { - var dfr = $.Deferred(); - if ( !this.util.is_bool(mode) ) { - mode = true; - } - this.loading = mode; - //Pause/Resume slideshow - if ( this.slideshow_active() ) { - this.slideshow_pause(mode); - } - //Set CSS class on DOM element - var m = ( mode ) ? 'addClass' : 'removeClass'; - $(this.dom_get())[m]('loading'); - if ( mode ) { - //Loading transition - this.get_theme().transition('load').always(function() { - dfr.resolve(); - }); - } else { - dfr.resolve(); - } - return dfr.promise(); - }, - - /** - * Unset loading mode - * @see set_loading() - * @return jQuery.Promise Promise that resovles when loading mode is set - */ - unset_loading: function() { - return this.set_loading(false); - }, - - /** - * Retrieve loading status - * @return bool Loading status (Default: FALSE) - */ - get_loading: function() { - return ( this.util.is_bool(this.loading) ) ? this.loading : false; - }, - - /** - * Check if viewer is currently loading content - * @return bool Loading status (Default: FALSE) - */ - is_loading: function() { - return this.get_loading(); - }, - - /* Display */ - - /** - * Display content in viewer - * @param Content_Item item Item to show - * @param obj options (optional) Display options - */ - show: function(item) { - this.item_queued = item; - var fin_set = 'show_deferred'; - //Validate theme - var vt = 'theme_valid'; - var valid = true; - if ( !this.has_attribute(vt)) { - valid = this.set_attribute(vt, ( this.get_theme() && this.get_theme().get_template().get_layout(false) ) ); - } else { - valid = this.get_attribute(vt, true); - } - - if ( !valid ) { - this.close(); - return false; - } - var v = this; - var fin = function() { - //Lock viewer - v.lock(); - //Reset callback flag (for new lock) - v.set_status(fin_set, false); - //Validate request - if ( !v.set_item(v.item_queued) ) { - v.close(); - return false; - } - //Add item to history stack - v.history_add(); - //Activate - v.set_active(); - //Display - v.render(); - } - if ( !this.is_locked() ) { - fin(); - } else if ( !this.get_status(fin_set) ) { - //Set flag to avoid duplicate callbacks - this.set_status(fin_set); - this.get_lock().always(function() { - fin(); - }); - } - }, - - /* History Management */ - - history_handle: function(e) { - var state = e.originalEvent.state; - //Load item - if ( this.util.is_int(state.item, false) ) { - this.get_parent().get_item(state.item).show({'event': e}); - this.trigger('item-change'); - } else { - var count = this.history_get(true); - //Reset count - this.history_set(0); - //Close viewer - if ( -1 != count ) { - this.close(); - } - } - }, - - history_get: function(full) { - return this.get_status('history_count', full); - }, - history_set: function(val) { - return this.set_status('history_count', val); - }, - history_add: function() { - if ( !history.pushState ) { - return false; - } - //Get display options - var item = this.get_item(); - var opts = item.get_attribute('options_show'); - //Save history state - var count = ( this.history_get() ) ? this.history_get(true) : 0; - if ( !this.util.in_obj(opts, 'event') ) { - //Create state - var state = { - 'viewer': this.get_id(), - 'item': null, - 'count': count - }; - //Init: Save viewer state - if ( !count ) { - history.replaceState(state, null); - } - //Always: Save item state - state.item = this.get_parent().save_item(item); - state.count = ++count; - history.pushState(state, ''); - } else { - var e = opts.event.originalEvent; - if ( this.util.in_obj(e, 'state') && this.util.in_obj(e.state, 'count') ) { - count = e.state.count; - } - } - //Save history item count - this.history_set(count); - }, - history_reset: function() { - var count = this.history_get(true); - if ( count ) { - //Clear history status - this.history_set(-1); - //Restore history stack - history.go( -1 * count ); - } - }, - - /** - * Check if viewer is currently open - * Checks if node is actually visible in DOM - * @return bool TRUE if viewer is open, FALSE otherwise - */ - is_open: function() { - return ( this.dom_get().css('display') == 'none' ) ? false : true; - }, - - /** - * Load output into DOM - */ - render: function() { - //Get theme output - var v = this; - var thm = this.get_theme(); - v.dom_prep(); - //Register theme event handlers - if ( !this.get_status('render-events') ) { - this.set_status('render-events'); - thm - //Loading - .on('render-loading', function(ev, thm) { - var dfr = $.Deferred(); - if ( !v.is_active() ) { - dfr.reject(); - return dfr.promise(); - } - var set_pos = function() { - //Set position - v.dom_get().css('top', $(window).scrollTop()); - }; - var always = function() { - //Set loading flag - v.set_loading().always(function() { - dfr.resolve(); - }); - }; - if ( v.is_open() ) { - thm.transition('unload') - .fail(function() { - set_pos(); - thm.dom_get_tag('item', 'content').attr('style', ''); - }) - .always(always); - } else { - thm.transition('open') - .always(function() { - always(); - v.events_open(); - v.open = true; - }) - .fail(function() { - set_pos(); - //Fallback open - v.get_overlay().show(); - v.dom_get().show(); - }); - } - return dfr.promise(); - }) - //Complete - .on('render-complete', function(ev, thm) { - //Stop if viewer not active - if ( !v.is_active() ) { - return false; - } - //Set classes - var d = v.dom_get(); - var classes = ['item_single', 'item_multi']; - var ms = ['addClass', 'removeClass']; - if ( !v.get_item().get_group().is_single() ) { - ms.reverse(); - } - $.each(ms, function(idx, val) { - d[val](classes[idx]); - }); - //Bind events - v.events_complete(); - //Transition - thm.transition('complete') - .fail(function() { - //Autofit content - if ( v.get_attribute('autofit', true) ) { - var dims = $.extend({'display': 'inline-block'}, thm.get_item_dimensions()); - var tag = thm.dom_get_tag('item', 'content').css(dims); - } - }) - .always(function() { - //Unset loading flag - v.unset_loading(); - //Trigger event - v.trigger('render-complete'); - //Set viewer as initialized - v.init = true; - }); - }); - } - //Render - thm.render(); - }, - - /** - * Retrieve container element - * Creates default container element if not yet created - * @return jQuery Container element - */ - dom_get_container: function() { - var sel = this.get_attribute('container'); - //Set default container - if ( this.util.is_empty(sel) ) { - sel = '#' + this.add_ns('wrap'); - } - //Add default container to DOM if not yet present - var c = $(sel); - if ( !c.length ) { - //Prepare ID - var id = ( sel.indexOf('#') === 0 ) ? sel.substr(1) : sel; - //Add element - c = $('
', {'id': id}).appendTo('body'); - } - return c; - }, - - /** - * Custom Viewer DOM initialization - */ - dom_init: function() { - //Create element & add to DOM - //Save element to instance - var d = this.dom_set($('
', { - 'id': this.get_id(true), - 'class': this.get_ns() - })).appendTo(this.dom_get_container()).hide(); - //Add theme classes - var thm = this.get_theme(); - d.addClass(thm.get_classes(' ')); - //Add theme layout (basic) - var v = this; - if ( !this.get_status('render-init') ) { - this.set_status('render-init'); - thm.on('render-init', function(ev) { - //Add rendered theme layout to viewer DOM - v.dom_put('layout', ev.data); - }); - } - thm.render(true); - }, - - /** - * Prepare DOM for viewer - */ - dom_prep: function(mode) { - var m = ( this.util.is_bool(mode) && !mode ) ? 'removeClass' : 'addClass'; - $('html')[m](this.util.add_prefix('overlay')); - }, - - /** - * Restore DOM - * Required after viewer is closed - */ - dom_restore: function() { - this.dom_prep(false); - }, - - /* Layout */ - - get_layout: function() { - var ret = this.dom_get('layout', true, { - 'put_success': function() { - $(this).hide(); - } - }); - return ret; - }, - - /* Animation */ - - animation_enabled: function() { - return this.get_attribute('animate', true); - }, - - /* Overlay */ - - /** - * Determine if overlay is enabled for viewer - * @return bool TRUE if overlay is enabled, FALSE otherwise - */ - overlay_enabled: function() { - var ov = this.get_attribute('overlay_enabled'); - return ( this.util.is_bool(ov) ) ? ov : false; - }, - - /** - * Retrieve overlay DOM element - * @return jQuery Overlay element (NULL if no overlay set for viewer) - */ - get_overlay: function() { - var o = null; - var v = this; - if ( this.overlay_enabled() ) { - o = this.dom_get('overlay', true, { - 'put_success': function() { - $(this).hide().css('opacity', v.get_attribute('overlay_opacity')); - } - }); - } - return $(o); - }, - - unload: function() { - - }, - - /** - * Reset viewer - */ - reset: function() { - //Hide viewer - this.dom_get().hide(); - //Restore DOM - this.dom_restore(); - //History - this.history_reset(); - //Item - this.clear_item(); - //Reset properties - this.set_active(false); - this.set_loading(false); - this.slideshow_stop(); - this.keys_disable(); - //Clear for next item - this.get_status('item_working', true).resolve(); - }, - - /* Content */ - - get_labels: function() { - return this.get_attribute('labels', {}); - }, - - get_label: function(name) { - var lbls = this.get_labels(); - return ( name in lbls ) ? lbls[name] : ''; - }, - - /* Interactivity */ - - /** - * Initialize event handlers upon opening lightbox - */ - events_open: function() { - //Keyboard bindings - this.keys_enable(); - if ( this.open ) { - return false; - } - - //Control event bubbling - var l = this.get_layout(); - l.children().click(function(ev) { - ev.stopPropagation(); - }); - - /* Close */ - var v = this; - var close = function() { - v.close(); - } - //Layout - l.click(close); - //Overlay - this.get_overlay().click(close); - //Fire event - this.trigger('events-open'); - }, - - /** - * Initialize event handlers upon completing lightbox rendering - */ - events_complete: function() { - if ( this.init ) { - return false; - } - //Fire event - this.trigger('events-complete'); - }, - - keys_enable: function(mode) { - if ( !this.util.is_bool(mode) ) { - mode = true; - } - var e = ['keyup', this.util.get_prefix()].join('.'); - var v = this; - var h = function(ev) { - return v.keys_control(ev); - } - if ( mode ) { - $(document).on(e, h); - } else { - $(document).off(e); - } - }, - - keys_disable: function() { - this.keys_enable(false); - }, - - keys_control: function(ev) { - var handlers = { - 27: this.close, - 37: this.item_prev, - 39: this.item_next - }; - if ( ev.which in handlers ) { - handlers[ev.which].call(this); - return false; - } - }, - - /** - * Check if slideshow functionality is enabled - * @return bool TRUE if slideshow is enabled, FALSE otherwise - */ - slideshow_enabled: function() { - var o = this.get_attribute('slideshow_enabled'); - return ( this.util.is_bool(o) && o && this.get_item() && !this.get_item().get_group().is_single() ) ? true : false; - }, - - /** - * Checks if slideshow is currently active - * @return bool TRUE if slideshow is active, FALSE otherwise - */ - slideshow_active: function() { - return ( this.slideshow_enabled() && ( this.get_attribute('slideshow_active') || ( !this.init && this.get_attribute('slideshow_autostart') ) ) ) ? true : false; - }, - - /** - * Clear slideshow timer - */ - slideshow_clear_timer: function() { - clearInterval(this.get_attribute('slideshow_timer')); - }, - - /** - * Start slideshow timer - * @param function callback Callback function - */ - slideshow_set_timer: function(callback) { - this.set_attribute('slideshow_timer', setInterval(callback, this.get_attribute('slideshow_duration') * 1000)); - }, - - /** - * Start Slideshow - */ - slideshow_start: function() { - if ( !this.slideshow_enabled() ) { - return false; - } - this.set_attribute('slideshow_active', true); - this.dom_get().addClass('slideshow_active'); - //Clear residual timers - this.slideshow_clear_timer(); - //Start timer - var v = this; - this.slideshow_set_timer(function() { - //Pause slideshow until next item fully loaded - v.slideshow_pause(); - - //Show next item - v.item_next(); - }); - this.trigger('slideshow-start'); - }, - - /** - * Stop Slideshow - * @param bool full (optional) Full stop (TRUE) or pause (FALSE) (Default: TRUE) - */ - slideshow_stop: function(full) { - if ( !this.util.is_bool(full) ) { - full = true; - } - if ( full ) { - this.set_attribute('slideshow_active', false); - this.dom_get().removeClass('slideshow_active'); - } - //Kill timers - this.slideshow_clear_timer(); - this.trigger('slideshow-stop'); - }, - - slideshow_toggle: function() { - if ( !this.slideshow_enabled() ) { - return false; - } - if ( this.slideshow_active() ) { - this.slideshow_stop(); - } else { - this.slideshow_start(); - } - this.trigger('slideshow-toggle'); - }, - - /** - * Pause Slideshow - * @param bool mode (optional) Pause (TRUE) or Resume (FALSE) slideshow (default: TRUE) - */ - slideshow_pause: function(mode) { - //Validate - if ( !this.util.is_bool(mode) ) { - mode = true; - } - //Set viewer slideshow properties - if ( this.slideshow_active() ) { - if ( !mode ) { - //Slideshow resumed - this.slideshow_start(); - } else { - //Slideshow paused - this.slideshow_stop(false); - } - } - this.trigger('slideshow-pause'); - }, - - /** - * Resume slideshow - */ - slideshow_resume: function() { - this.slideshow_pause(false); - }, - - /** - * Next item - */ - item_next: function() { - var g = this.get_item().get_group(true); - var v = this; - var ev = 'item-next'; - var st = ['events', 'viewer', ev].join('_'); - //Setup event handler - if ( !g.get_status(st) ) { - g.set_status(st); - g.on(ev, function(e) { - v.trigger(e.type); - }); - } - g.show_next(); - }, - - /** - * Previous item - */ - item_prev: function() { - var g = this.get_item().get_group(true); - var v = this; - var ev = 'item-prev'; - var st = ['events', 'viewer', ev].join('_'); - if ( !g.get_status(st) ) { - g.set_status(st); - g.on(ev, function() { - v.trigger(ev); - }); - } - g.show_prev(); - }, - - /** - * Close viewer - */ - close: function() { - //Deactivate - this.set_active(false); - var v = this; - var thm = this.get_theme(); - thm.transition('unload') - .always(function() { - thm.transition('close', true).always(function() { - //End processes - v.reset(); - v.trigger('close'); - }); - }) - .fail(function() { - thm.dom_get_tag('item', 'content').attr('style', ''); - }); - return false; - } -}; - -View.Viewer = Component.extend(Viewer); - -/** - * Content group - * @param obj options Init options - */ -var Group = { - /* Configuration */ - - _slug: 'group', - _reciprocal: true, - _refs: { - 'current': 'Content_Item' - }, - - /* References */ - - current: null, - - /* Properties */ - - /** - * Selector for getting group items - * @var string - */ - selector: null, - - /* Methods */ - - /* Init */ - - register_hooks: function() { - var t = this; - this.on(['item-prev', 'item-next'], function() { - t.trigger('item-change'); - }); - }, - - /* Properties */ - - /** - * Retrieve selector for group items - * @return string Group items selector - */ - get_selector: function() { - if ( this.util.is_empty(this.selector) ) { - //Build selector - this.selector = this.util.format('a[%s="%s"]', this.dom_get_attribute(), this.get_id()); - } - return this.selector; - }, - - /** - * Retrieve group items - */ - get_items: function() { - var items = ( !this.util.is_empty(this.get_id()) ) ? $(this.get_selector()) : this.get_current().dom_get(); - return items; - }, - - /** - * Retrieve item at specified index - * If no index specified, first item is returned - * @param int idx Index of item to return - * @return Content_Item Item - */ - get_item: function(idx) { - //Validation - if ( !this.util.is_int(idx) ) { - idx = 0; - } - //Retrieve all items - var items = this.get_items(); - //Validate index - var max = this.get_size() - 1; - if ( idx > max ) { - idx = max; - } - //Return specified item - return items.get(idx); - }, - - /** - * Retrieve (zero-based) position of specified item in group - * @param Content_Item item Item to locate in group - * @return int Index position of item in group (-1 if item not in group) - */ - get_pos: function(item) { - if ( this.util.is_empty(item) ) { - //Get current item - item = this.get_current(); - } - return ( this.util.is_type(item, View.Content_Item) ) ? this.get_items().index(item.dom_get()) : -1; - }, - - /** - * Retrieve current item - * @return Content_Item Current item - */ - get_current: function() { - //Sanitize - if ( !this.util.is_empty(this.current) && !this.util.is_type(this.current, View.Content_Item) ) { - this.current = null; - } - return this.current; - }, - - /** - * Sets current group item - * @param Content_Item item Item to set as current - */ - set_current: function(item) { - //Validate - if ( this.util.is_type(item, View.Content_Item) ) { - //Set current item - this.current = item; - } - }, - - get_next: function(item) { - //Validate - if ( !this.util.is_type(item, View.Content_Item) ) { - item = this.get_current(); - } - if ( this.get_size() == 1 ) { - return item; - } - var next = null; - var pos = this.get_pos(item); - if ( pos != -1 ) { - pos = ( pos + 1 < this.get_size() ) ? pos + 1 : 0; - if ( 0 != pos || item.get_viewer().get_attribute('loop') ) { - next = this.get_item(pos); - } - } - return next; - }, - - get_prev: function(item) { - //Validate - if ( !this.util.is_type(item, View.Content_Item) ) { - item = this.get_current(); - } - if ( this.get_size() == 1 ) { - return item; - } - var prev = null; - var pos = this.get_pos(item); - if ( pos != -1 && ( 0 != pos || item.get_viewer().get_attribute('loop') ) ) { - if ( pos == 0 ) { - pos = this.get_size(); - } - pos -= 1; - prev = this.get_item(pos); - } - return prev; - }, - - show_next: function(item) { - if ( this.get_size() > 1 ) { - //Retrieve item - var next = this.get_next(item); - if ( !next ) { - if ( !this.util.is_type(item, View.Content_Item) ) { - item = this.get_current(); - } - item.get_viewer().close(); - } - var i = this.get_parent().get_item(next); - //Update current item - this.set_current(i); - //Show item - i.show(); - //Fire event - this.trigger('item-next'); - } - }, - - show_prev: function(item) { - if ( this.get_size() > 1 ) { - //Retrieve item - var prev = this.get_prev(item); - if ( !prev ) { - if ( !this.util.is_type(item, View.Content_Item) ) { - item = this.get_current(); - } - item.get_viewer().close(); - } - var i = this.get_parent().get_item(prev); - //Update current item - this.set_current(i); - //Show item - i.show(); - //Fire event - this.trigger('item-prev'); - } - }, - - /** - * Retrieve total number of items in group - * @return int Number of items in group - */ - get_size: function() { - return this.get_items().length; - }, - - is_single: function() { - return ( this.get_size() == 1 ); - } -}; - -View.Group = Component.extend(Group); - -/** - * Content Handler - * @param obj options Init options - */ -var Content_Handler = { - - /* Configuration */ - - _slug: 'content_handler', - _refs: { - 'item': 'Content_Item' - }, - - /* References */ - - item: null, - - /* Properties */ - - /** - * Raw layout template - * @var string - */ - template: '', - - /* Methods */ - - /* Item */ - - /** - * Check if item instance set for type - * @uses get_item() - * @uses clear_item() to remove invalid item values - * @return bool TRUE if valid item set, FALSE otherwise - */ - has_item: function() { - return ( this.util.is_empty(this.get_item()) ) ? false : true; - }, - - /** - * Retrieve item instance set on type - * @uses get_component() - * @return mixed Content_Item if valid item set, NULL otherwise - */ - get_item: function() { - return this.get_component('item', true, false); - }, - - /** - * Set item instance for type - * Items are only meant to be set/used while item is being processed - * @uses set_component() - * @param Content_Item item Item instance - * @return obj|null Item instance if item successfully set, NULL otherwise - */ - set_item: function(item) { - //Set reference - var r = this.set_component('item', item); - return r; - }, - - /** - * Clear item instance from type - * Sets value to NULL - */ - clear_item: function() { - this.item = null; - }, - - /* Evaluation */ - - /** - * Check if item matches content handler - * @param object item Content_Item instance to check for type match - * @return bool TRUE if type matches, FALSE otherwise - */ - match: function(item) { - //Validate - var attr = 'match'; - var m = this.get_attribute(attr); - //Stop processing types with no matching algorithm - if ( !this.util.is_empty(m) ) { - //Process regex patterns - - //String-based - if ( this.util.is_string(m) ) { - //Create new regexp object - m = new RegExp(m, "i"); - this.set_attribute(attr, m); - } - //RegExp based - if ( this.util.is_type(m, RegExp) ) { - return m.test(item.get_uri()); - } - //Process function - if ( this.util.is_func(m) ) { - return ( m.call(this, item) ) ? true : false; - } - } - //Default - return false; - }, - - /* Processing/Output */ - - /** - * Render output to display item - * @param Content_Item item Item to render output for - * @return obj jQuery.Promise that is resolved when item is rendered - */ - render: function(item) { - var dfr = $.Deferred(); - //Validate - var ret = this.call_attribute('render', item); - if ( this.util.is_promise(ret) ) { - ret.done(function(output) { - dfr.resolve(output); - }); - } else { - //String format - if ( this.util.is_string(ret) ) { - ret = this.util.format(ret, item.get_uri()); - } - //Resolve deferred immediately - dfr.resolve(ret); - } - return dfr.promise(); - } -}; - -View.Content_Handler = Component.extend(Content_Handler); - -/** - * Content Item - * @param obj options Init options - */ -var Content_Item = { - /* Configuration */ - - _slug: 'content_item', - _reciprocal: true, - _refs: { - 'viewer': 'Viewer', - 'group': 'Group', - 'type': 'Content_Handler' - }, - _containers: ['group'], - - _attr_default: { - source: null, - permalink: null, - dimensions: null, - title: '', - group: null, - internal: false, - output: null - }, - - /* References */ - - group: null, - viewer: null, - type: null, - - /* Properties */ - - data: null, - - /* Init */ - - _c: function(el) { - //Save element to instance - this.dom_set(el); - //Default initialization - this._super(); - }, - - /* Methods */ - - /*-** Attributes **-*/ - - /** - * Build default attributes - * Populates attributes with asset properties (attachments) - * Overrides super class method - * @uses Component.init_default_attributes() - */ - init_default_attributes: function() { - this._super(); - //Add asset properties - var d = this.dom_get(); - var key = d.attr('href') || null; - var assets = this.get_parent().assets || null; - //Merge asset data with default attributes - if ( this.util.is_string(key) ) { - var attrs = [{}, this._attr_default, {'permalink': key}]; - if ( this.util.is_obj(assets) ) { - var t = this; - var get_assets = function(key, raw) { - var ret = {}; - if ( key in assets && t.util.is_obj(assets[key]) ) { - var ret = assets[key]; - if ( t.util.is_string(raw) ) { - var e = '_entries'; - if ( !( e in ret ) || -1 == $.inArray(raw, ret[e]) ) { - ret = {}; - } - } - } - return ret; - }; - var asset = get_assets(key); - if ( this.util.is_empty(asset) && ( kpos = key.indexOf('?') ) && kpos != -1 ) { - var key_base = key.substr(0, kpos); - asset = get_assets(key_base, key); - } - if ( !this.util.is_empty(asset) ) { - attrs.push(asset); - } - } - this._attr_default = $.extend.apply(this, attrs); - } - return this._attr_default; - }, - - /*-** Properties **-*/ - - /** - * Retrieve item output - * Output generated based on content handler if not previously generated - * @uses get_attribute() to retrieve cached output - * @uses set_attribute() to cache generated output - * @uses get_type() to retrieve item type - * @uses Content_Handler.render() to generate item output - * @return obj jQuery.Promise that is resolved when output is retrieved - */ - get_output: function() { - var dfr = $.Deferred(); - //Check for cached output - var ret = this.get_attribute('output'); - if ( this.util.is_string(ret) ) { - dfr.resolve(ret); - } else if ( this.has_type() ) { - //Render output from scratch (if necessary) - //Get item type - var type = this.get_type(); - //Render type-based output - var item = this; - type.render(this).done(function(output) { - //Cache output - item.set_output(output); - dfr.resolve(output); - }); - } else { - dfr.resolve(''); - } - return dfr.promise(); - }, - - /** - * Cache output for future retrieval - * @uses set_attribute() to cache output - */ - set_output: function(out) { - if ( this.util.is_string(out, false) ) { - this.set_attribute('output', out); - } - }, - - /** - * Retrieve item output - * Alias for `get_output()` - * @return jQuery.Promise Deferred that is resolved when content is retrieved - */ - get_content: function() { - return this.get_output(); - }, - - /** - * Retrieve item URI - * @param string mode (optional) Which URI should be retrieved - * > source: Media source - * > permalink: Item permalink - * @return string Item URI - */ - get_uri: function(mode) { - //Validate - if ( $.inArray(mode ,['source', 'permalink']) == -1 ) { - mode = 'source'; - } - //Retrieve URI - var ret = this.get_attribute(mode); - if ( !this.util.is_string(ret) ) { - ret = ( 'source' == mode ) ? this.get_attribute('permalink') : ''; - } - return ret; - }, - - /** - * Retrieve item title - */ - get_title: function() { - var prop = 'title'; - var prop_cached = prop + '_cached'; - //Check for cached value - if ( this.has_attribute(prop_cached) ) { - return this.get_attribute(prop_cached, ''); - } - - var title = ''; - var sel_cap = '.wp-caption-text'; - //Generate title from DOM values - var dom = this.dom_get(); - - //Standalone link - if ( dom.length && !this.in_gallery() ) { - //Link title - title = dom.attr(prop); - - //Caption - if ( !title ) { - title = dom.siblings(sel_cap).html(); - } - } - - //Saved attributes - if ( !title ) { - var props = ['caption', 'title']; - for ( var x = 0; x < props.length; x++ ) { - title = this.get_attribute(props[x], ''); - if ( !this.util.is_empty(title) ) { - break; - } - } - } - - //Fallbacks - if ( !title && dom.length ) { - //Alt attribute - title = dom.find('img').first().attr('alt'); - - //Element text - if ( !title ) { - title = dom.text(); - } - } - - //Validate - if ( !this.util.is_string(title, false) ) { - title = ''; - } - - //Cache retrieved value - this.set_attribute(prop_cached, title); - //Return value - return title; - }, - - /** - * Retrieve item dimensions - * @return obj Item `width` and `height` properties (px) - */ - get_dimensions: function() { - return $.extend({'width': 0, 'height': 0}, this.get_attribute('dimensions'), {}); - }, - - /** - * Save item data to instance - * Item data is saved when rendered - * @param mixed data Item data (property cleared if NULL) - */ - set_data: function(data) { - this.data = data; - }, - - /** - * Determine gallery type - * @return string|null Gallery type ID (NULL if item not in gallery) - */ - gallery_type: function() { - var ret = null; - var types = { - 'wp': '.gallery-icon', - 'ngg': '.ngg-gallery-thumbnail' - }; - - var dom = this.dom_get(); - for ( var type in types ) { - if ( dom.parent(types[type]).length > 0 ) { - ret = type; - break; - } - } - return ret; - }, - - /** - * Check if current link is part of a gallery - * @param string gType (optional) Gallery type to check for - * @return bool TRUE if link is part of (specified) gallery (FALSE otherwise) - */ - in_gallery: function(gType) { - var type = this.gallery_type(); - //No gallery - if ( null == type ) { - return false; - } - //Boolean check - if ( !this.util.is_string(gType) ) { - return true; - } - //Check for specific gallery type - return ( gType == type ) ? true : false; - }, - - /*-** Component References **-*/ - - /* Viewer */ - - get_viewer: function() { - return this.get_component('viewer'); - }, - - /** - * Sets item's viewer property - * @uses View.get_viewer() to retrieve global viewer - * @uses this.viewer to save item's viewer - * @param string|View.Viewer v Viewer to set for item - * > Item's viewer is reset if invalid viewer provided - */ - set_viewer: function(v) { - return this.set_component('viewer', v); - }, - - /* Group */ - - /** - * Retrieve item's group - * @param bool set_current (optional) Sets item as current item in group (Default: FALSE) - * @return View.Group|bool Group reference item belongs to (FALSE if no group) - */ - get_group: function(set_current) { - var prop = 'group'; - //Check if group reference already set - var g = this.get_component(prop, true, false, false); - if ( g ) { - } else { - //Set empty group if no group exists - g = this.set_component(prop, new View.Group()); - set_current = true; - } - if ( !!set_current ) { - g.set_current(this); - } - return g; - }, - - /** - * Sets item's group property - * @uses View.get_group() to retrieve global group - * @uses this.group to set item's group - * @param string|View.Group g Group to set for item - * > Item's group is reset if invalid group provided - */ - set_group: function(g) { - //If group ID set, get object reference - if ( this.util.is_string(g) ) { - g = this.get_parent().get_group(g); - } - - //Set (or clear) group property - this.group = ( this.util.is_type(g, View.Group) ) ? g : false; - }, - - /* Content Handler */ - - /** - * Retrieve item type - * @uses get_component() to retrieve saved reference to Content_Handler instance - * @uses View.get_content_handler() to determine item content handler (if necessary) - * @return Content_Handler|null Content Handler of item (NULL no valid type exists) - */ - get_type: function() { - var t = this.get_component('type', false, false, false); - if ( !t ) { - t = this.set_type(this.get_parent().get_content_handler(this)); - } - return t; - }, - - /** - * Save content handler reference - * @uses set_component() to save type reference - * @return Content_Handler|null Saved content handler (NULL if invalid) - */ - set_type: function(type) { - return this.set_component('type', type); - }, - - /** - * Check if content handler exists for item - * @return bool TRUE if content handler exists, FALSE otherwise - */ - has_type: function() { - var ret = !this.util.is_empty(this.get_type()); - return ret; - }, - - /* Actions */ - - /** - * Display item in viewer - * @uses get_viewer() to retrieve viewer instance for item - * @uses Viewer.show() to display item in viewer - * @param obj options (optional) Options - */ - show: function(options) { - //Validate content handler - if ( !this.has_type() ) { - return false; - } - //Set display options - this.set_attribute('options_show', options); - //Retrieve viewer - var v = this.get_viewer(); - //Load item - var ret = v.show(this); - return ret; - }, - - reset: function() { - this.set_attribute('options_show', null); - } -}; - -View.Content_Item = Component.extend(Content_Item); - -/** - * Modeled Component - */ -var Modeled_Component = { - - _slug: 'modeled_component', - - /* Methods */ - - /* Attributes */ - - /** - * Retrieve attribute - * Gives priority to model values - * @see Component.get_attribute() - * @param string key Attribute to retrieve - * @param mixed def (optional) Default value (Default: NULL) - * @param bool check_model (optional) Check model for value (Default: TRUE) - * @param bool enforce_type (optional) Return value data type should match default value data type (Default: TRUE) - * @return mixed Attribute value - */ - get_attribute: function(key, def, check_model, enforce_type) { - //Validate - if ( !this.util.is_string(key) ) { - //Invalid requests sent straight to super method - return this._super(key, def, enforce_type); - } - if ( !this.util.is_bool(check_model) ) { - check_model = true; - } - var ret = null; - //Check model for attribute - if ( check_model ) { - var m = this.get_ancestor(key, false); - if ( this.util.in_obj(m, key) ) { - ret = m[key]; - } - } - //Check standard attributes as fallback - if ( null == ret ) { - ret = this._super(key, def, enforce_type); - } - return ret; - }, - - /** - * Set attribute value - * Gives priority to model values - * @see Component.set_attribute() - * @param string key Attribute to set - * @param mixed val Value to set for attribute - * @param bool|obj use_model (optional) Set the value on the model (Default: TRUE) - * > bool: Set attribute on current model (TRUE) or as standard attribute (FALSE) - * > obj: Model object to set attribute on - * @return mixed Attribute value - */ - set_attribute: function(key, val, use_model) { - //Validate - if ( ( !this.util.is_string(key) ) || !this.util.is_set(val) ) { - return false; - } - if ( !this.util.is_bool(use_model) && !this.util.is_obj(use_model) ) { - use_model = true; - } - //Determine where to set attribute - if ( !!use_model ) { - var model = this.util.is_obj(use_model) ? use_model : this.get_model(); - - //Set attribute in model - model[key] = val; - } else { - //Set as standard attribute - this._super(key, val); - } - return val; - }, - - - /* Model */ - - /** - * Retrieve Template model - * @return obj Model (Default: Empty object) - */ - get_model: function() { - var m = this.get_attribute('model', null, false); - if ( !this.util.is_obj(m) ) { - //Set default value - m = {}; - this.set_attribute('model', m, false); - } - return m; - }, - - - /** - * Check if instance has model - * @return bool TRUE if model is set, FALSE otherwise - */ - has_model: function() { - return ( this.util.is_empty( this.get_model() ) ) ? false : true; - }, - - - - /** - * Check if specified attribute exists in model - * @param string key Attribute to check for - * @return bool TRUE if attribute exists, FALSE otherwise - */ - in_model: function(key) { - return ( this.util.in_obj(this.get_model(), key) ) ? true : false; - }, - - /** - * Retrieve all ancestor models - * @param bool inc_current (optional) Include current model in list (Default: FALSE) - * @return array Theme ancestor models (Closest parent first) - */ - get_ancestors: function(inc_current) { - var ret = []; - var m = this.get_model(); - while ( this.util.is_obj(m) ) { - ret.push(m); - m = ( this.util.in_obj(m, 'parent') && this.util.is_obj(m.parent) ) ? m.parent : null; - } - //Remove current model from list - if ( !inc_current ) { - ret.shift(); - } - return ret; - }, - - /** - * Retrieve first ancestor of current theme with specified attribute - * > Current model is also evaluated - * @param string attr Attribute to search ancestors for - * @param bool safe_mode (optional) Return current model if no matching ancestor found (Default: TRUE) - * @return obj Theme ancestor (Default: Current theme model) - */ - get_ancestor: function(attr, safe_mode) { - //Validate - if ( !this.util.is_string(attr) ) { - return false; - } - if ( !this.util.is_bool(safe_mode) ) { - safe_mode = true; - } - var mcurr; - var m = mcurr = this.get_model(); - var found = false; - while ( this.util.is_obj(m) ) { - //Check if attribute exists in model - if ( this.util.in_obj(m, attr) && !this.util.is_empty(m[attr]) ) { - found = true; - break; - } - //Get next model - m = ( this.util.in_obj(m, 'parent') ) ? m['parent'] : null; - } - if ( !found ) { - if ( safe_mode ) { - //Use current model as fallback - if ( this.util.is_empty(m) ) { - m = mcurr; - } - //Add attribute to object - if ( !this.util.in_obj(m, attr) ) { - m[attr] = null; - } - } else { - m = null; - } - } - return m; - } - -}; - -Modeled_Component = Component.extend(Modeled_Component); - -/** - * Theme - */ -var Theme = { - - /* Configuration */ - - _slug: 'theme', - _refs: { - 'viewer': 'Viewer', - 'template': 'Template' - }, - _models: {}, - - _containers: ['viewer'], - - _attr_default: { - template: null, - model: null - }, - - /* References */ - - viewer: null, - template: null, - - /* Methods */ - - /** - * Custom constructor - * @see Component._c() - */ - _c: function(id, attributes, viewer) { - //Validate - if ( arguments.length == 1 && this.util.is_type(arguments[0], View.Viewer) ) { - viewer = arguments[0]; - id = null; - } - //Pass parameters to parent constructor - this._super(id, attributes); - - //Set viewer instance - this.set_viewer(viewer); - - //Set theme model - this.set_model(id); - }, - - /* Viewer */ - - get_viewer: function() { - return this.get_component('viewer', false, true, false); - }, - - /** - * Sets theme's viewer property - * @uses View.get_viewer() to retrieve global viewer - * @uses this.viewer to save item's viewer - * @param string|View.Viewer v Viewer to set for item - * > Theme's viewer is reset if invalid viewer provided - */ - set_viewer: function(v) { - return this.set_component('viewer', v); - }, - - /* Template */ - - /** - * Retrieve template instance - * @return Template instance - */ - get_template: function() { - //Get saved template - var ret = this.get_component('template', true, false, false); - //Template needs to be initialized - if ( this.util.is_empty(ret) ) { - //Pass model to Template instance - var attr = { 'theme': this, 'model': this.get_model() }; - ret = this.set_component('template', new View.Template(attr)); - } - return ret; - }, - - /** - * Retrieve tags from template - * All tags will be retrieved by default - * Specific tag/property instances can be retrieved as well - * @see Template.get_tags() - * @param string name (optional) Name of tags to retrieve - * @param string prop (optional) Specific tag property to retrieve - * @return array Tags in template - */ - get_tags: function(name, prop) { - return this.get_template().get_tags(name, prop); - }, - - /** - * Retrieve tag DOM elements - * @see Template.dom_get_tag() - */ - dom_get_tag: function(tag, prop) { - return $(this.get_template().dom_get_tag(tag, prop)); - }, - - /* Model */ - - /** - * Retrieve theme models - * @return obj Theme models - */ - get_models: function() { - return this._models; - }, - - /** - * Retrieve specified theme model - * @param string id (optional) Theme model to retrieve - * > Default model retrieved if ID is invalid/not set - * @return obj Specified theme model - */ - get_model: function(id) { - var ret = null; - //Pass request to superclass method - if ( !this.util.is_set(id) && this.util.is_obj( this.get_attribute('model', null, false) ) ) { - ret = this._super(); - } else { - //Retrieve matching theme model - var models = this.get_models(); - if ( !this.util.is_string(id) ) { - var id = this.get_parent().get_option('theme_default'); - } - //Select first theme model if specified model is invalid - if ( !this.util.in_obj(models, id) ) { - id = $.map(models, function(v, key) { return key; })[0]; - } - ret = models[id]; - } - return ret; - }, - - /** - * Set model for current theme instance - * @param string id (optional) Theme ID (Default theme retrieved if ID invalid) - */ - set_model: function(id) { - this.set_attribute('model', this.get_model(id), false); - //Set ID using model attributes (if necessary) - if ( !this.check_id(true) ) { - var m = this.get_model(); - if ( 'id' in m ) { - this.set_id(m.id); - } - } - }, - - /* Properties */ - - /** - * Generate class names for DOM node - * @param string rtype (optional) Return data type - * > Default: array - * > If string supplied: Joined classes delimited by parameter - * @uses get_class() to generate class names - * @uses Array.join() to convert class names array to string - * @return array Class names - */ - get_classes: function(rtype) { - //Build array of class names - var cls = []; - var thm = this; - //Include theme parent's class name - var models = this.get_ancestors(true); - $.each(models, function(idx, model) { - cls.push(thm.add_ns(model.id)); - }); - //Convert class names array to string - if ( this.util.is_string(rtype) ) { - cls = cls.join(rtype); - } - //Return class names - return cls; - }, - - /** - * Get custom measurement - * @param string attr Measurement to retrieve - * @param obj def (optional) Default value - * @return obj Attribute measurements - */ - get_measurement: function(attr, def) { - var meas = null; - //Validate - if ( !this.util.is_string(attr) ) { - return meas; - } - if ( !this.util.is_obj(def, false) ) { - def = {}; - } - //Manage cache - var attr_cache = this.util.format('%s_cache', attr); - var cache = this.get_attribute(attr_cache, {}, false); - var status = '_status'; - var item = this.get_viewer().get_item(); - var w = $(window); - //Check cache freshness - if ( !( status in cache ) || !this.util.is_obj(cache[status]) || cache[status].width != w.width() || cache[status].height != w.height() ) { - cache = {}; - } - if ( this.util.is_empty(cache) ) { - //Set status - cache[status] = { - 'width': w.width(), - 'height': w.height(), - 'index': [] - }; - } - //Retrieve cached values - var pos = $.inArray(item, cache[status].index); - if ( pos != -1 && pos in cache ) { - meas = cache[pos]; - } - //Generate measurement - if ( !this.util.is_obj(meas) ) { - //Get custom theme measurement - meas = this.call_attribute(attr); - if ( !this.util.is_obj(meas) ) { - //Retrieve fallback value - meas = this.get_measurement_default(attr); - } - } - //Normalize measurement - meas = ( this.util.is_obj(meas) ) ? $.extend({}, def, meas) : def; - //Cache measurement - pos = cache[status].index.push(item) - 1; - cache[pos] = meas; - this.set_attribute(attr_cache, cache, false); - //Return measurement (copy) - return $.extend({}, meas); - }, - - /** - * Get default measurement using attribute's default handler - * @param string attr Measurement attribute - * @return obj Measurement values - */ - get_measurement_default: function(attr) { - //Validate - if ( !this.util.is_string(attr) ) { - return null; - } - //Find default handler - attr = this.util.format('get_%s_default', attr); - if ( this.util.in_obj(this, attr) ) { - attr = this[attr]; - if ( this.util.is_func(attr) ) { - //Execute default handler - attr = attr.call(this); - } - } else { - attr = null; - } - return attr; - }, - - /** - * Retrieve theme offset - * @return obj Theme offset with `width` & `height` properties - */ - get_offset: function() { - return this.get_measurement('offset', { 'width': 0, 'height': 0}); - }, - - /** - * Generate default offset - * @return obj Theme offsets with `width` & `height` properties - */ - get_offset_default: function() { - var offset = { 'width': 0, 'height': 0 }; - var v = this.get_viewer(); - var vn = v.dom_get(); - //Clone viewer - var vc = vn - .clone() - .attr('id', '') - .css({'visibility': 'hidden', 'position': 'absolute', 'top': ''}) - .removeClass('loading') - .appendTo(vn.parent()); - //Get offset from layout node - var l = vc.find(v.dom_get_selector('layout')); - if ( l.length ) { - //Clear inline styles - l.find('*').css({ - 'width': '', - 'height': '', - 'display': '' - }); - //Resize content nodes - var tags = this.get_tags('item', 'content'); - if ( tags.length ) { - var offset_item = v.get_item().get_dimensions(); - //Set content dimensions - tags = $(l.find(tags[0].get_selector('full')).get(0)).css({'width': offset_item.width, 'height': offset_item.height}); - $.each(offset_item, function(key, val) { - offset[key] = -1 * val; - }); - } - - //Set offset - offset.width += l.width(); - offset.height += l.height(); - //Normalize - $.each(offset, function(key, val) { - if ( val < 0 ) { - offset[key] = 0; - } - }); - } - vc.empty().remove(); - return offset; - }, - - /** - * Retrieve theme margins - * @return obj Theme margin with `width` & `height` properties - */ - get_margin: function() { - return this.get_measurement('margin', {'width': 0, 'height': 0}); - }, - - /** - * Retrieve item dimensions - * Dimensions are adjusted to fit window (if necessary) - * @return obj Item dimensions with `width` & `height` properties - */ - get_item_dimensions: function() { - var v = this.get_viewer(); - var dims = v.get_item().get_dimensions(); - if ( v.get_attribute('autofit', false) ) { - //Get maximum dimensions - var margin = this.get_margin(); - var offset = this.get_offset(); - offset.height += margin.height; - offset.width += margin.width; - var max = {'width': $(window).width(), 'height': $(window).height() }; - if ( max.width > offset.width ) { - max.width -= offset.width; - } - if ( max.height > offset.height ) { - max.height -= offset.height; - } - //Get resize factor - var factor = Math.min(max.width / dims.width, max.height / dims.height); - //Resize dimensions - if ( factor < 1 ) { - $.each(dims, function(key, val) { - dims[key] = Math.round(dims[key] * factor); - }); - } - } - return $.extend({}, dims); - }, - - /** - * Retrieve theme dimensions - * @return obj Theme dimensions with `width` & `height` properties - */ - get_dimensions: function() { - var dims = this.get_item_dimensions(); - var offset = this.get_offset(); - $.each(dims, function(key, val) { - dims[key] += offset[key]; - }); - return dims; - }, - - /* Output */ - - /** - * Render Theme output - * @param bool init (optional) Initialize theme (Default: FALSE) - * @see Template.render() - */ - render: function(init) { - var thm = this; - var tpl = this.get_template(); - var st = 'events_render'; - if ( !this.get_status(st) ) { - this.set_status(st); - //Register events - tpl.on([ - 'render-init', - 'render-loading', - 'render-complete' - ], - function(ev) { - return thm.trigger(ev.type, ev.data); - }); - } - //Render template - tpl.render(init); - }, - - transition: function(event, clear_queue) { - var dfr = null; - var attr = 'transition'; - var v = this.get_viewer(); - var fx_temp = null; - var anim_on = v.animation_enabled(); - if ( v.get_attribute(attr, true) && this.util.is_string(event) ) { - var anim_stop = function() { - var l = v.get_layout(); - l.find('*').each(function() { - var el = $(this); - while ( el.queue().length ) { - el.stop(false, true); - } - }); - } - //Stop queued animations - if ( !!clear_queue ) { - anim_stop(); - } - //Get transition handlers - var attr_set = [attr, 'set'].join('_'); - var trns; - if ( !this.get_attribute(attr_set) ) { - var models = this.get_ancestors(true); - trns = []; - this.set_attribute(attr_set, true); - var thm = this; - $.each(models, function(idx, model) { - if ( attr in model && thm.util.is_obj(model[attr]) ) { - trns.push(model[attr]); - } - }); - //Merge transition handlers into current theme - trns.push({}); - trns = this.set_attribute(attr, $.extend.apply($, trns.reverse())); - } else { - trns = this.get_attribute(attr, {}); - } - if ( this.util.is_method(trns, event) ) { - //Disable animations if necessary - if ( !anim_on ) { - fx_temp = $.fx.off; - $.fx.off = true; - } - //Pass control to transition event - dfr = trns[event].call(this, v, $.Deferred()); - } - } - if ( !this.util.is_promise(dfr) ) { - dfr = $.Deferred(); - dfr.reject(); - } - dfr.always(function() { - //Restore animation state - if ( null !== fx_temp ) { - $.fx.off = fx_temp; - } - }); - return dfr.promise(); - } -}; - -View.Theme = Modeled_Component.extend(Theme); - -/** - * Template handler - * Parses and Builds layout from raw template - */ -var Template = { - /* Configuration */ - - _slug: 'template', - _reciprocal: true, - - _refs: { - 'theme': 'Theme' - }, - _containers: ['theme'], - - _attr_default: { - /** - * URI to layout (raw) file - * @var string - */ - layout_uri: '', - - /** - * Raw layout template - * @var string - */ - layout_raw: '', - /** - * Parsed layout - * Placeholders processed - * @var string - */ - layout_parsed: '', - /** - * Tags in template - * Populated once template has been parsed - * @var array - */ - tags: null, - /** - * Model to use for properties - * Usually reference to an object in other component - * @var obj - */ - model: null - }, - - /* References */ - - theme: null, - - /* Methods */ - - _c: function(attributes) { - this._super('', attributes); - }, - - get_theme: function() { - var ret = this.get_component('theme', true, false, false); - return ret; - }, - - /* Output */ - - /** - * Render output - * @param bool init (optional) Whether to initialize layout (TRUE) or render item (FALSE) (Default: FALSE) - * Events - * > render-init: Initialize template - * > render-loading: DOM elements created and item content about to be loaded - * > render-complete: Item content loaded, ready for display - */ - render: function(init) { - var v = this.get_theme().get_viewer(); - if ( !this.util.is_bool(init) ) { - init = false; - } - //Populate layout - if ( !init ) { - if ( !v.is_active() ) { - return false; - } - var item = v.get_item(); - if ( !this.util.is_type(item, View.Content_Item) ) { - v.close(); - return false; - } - //Iterate through tags and populate layout - if ( v.is_active() && this.has_tags() ) { - var loading_promise = this.trigger('render-loading'); - var tpl = this; - var tags = this.get_tags(), - tag_promises = []; - //Render Tag output - loading_promise.done(function() { - if ( !v.is_active() ) { - return false; - } - $.each(tags, function(idx, tag) { - if ( !v.is_active() ) { - return false; - } - tag_promises.push(tag.render(item).done(function(r) { - if ( !v.is_active() ) { - return false; - } - r.tag.dom_get().html(r.output); - })); - }); - //Fire event when all tags rendered - if ( !v.is_active() ) { - return false; - } - $.when.apply($, tag_promises).done(function() { - tpl.trigger('render-complete'); - }); - }); - } - } else { - //Get Layout (basic) - this.trigger('render-init', this.dom_get()); - } - }, - - /*-** Layout **-*/ - - /** - * Retrieve layout - * @param bool parsed (optional) TRUE retrieves parsed layout, FALSE retrieves raw layout (Default: TRUE) - * @return string Layout (HTML) - */ - get_layout: function(parsed) { - //Validate - if ( !this.util.is_bool(parsed) ) { - parsed = true; - } - //Determine which layout to retrieve (raw/parsed) - var l = ( parsed ) ? this.parse_layout() : this.get_attribute('layout_raw', ''); - return l; - }, - - /** - * Parse layout - * Converts template tags to HTML elements - * > Template tag properties saved to HTML elements for future initialization - * Returns saved layout if previously parsed - * @return string Parsed layout - */ - parse_layout: function() { - //Check for previously-parsed layout - var a = 'layout_parsed'; - var ret = this.get_attribute(a); - //Return cached layout immediately - if ( this.util.is_string(ret) ) { - return ret; - } - //Parse raw layout - ret = this.sanitize_layout( this.get_layout(false) ); - ret = this.parse_tags(ret); - //Save parsed layout - this.set_attribute(a, ret); - - //Return parsed layout - return ret; - }, - - /** - * Sanitize layout - * @param obj|string l Layout string or jQuery object - * @return obj|string Sanitized layout (Same data type that was passed to method) - */ - sanitize_layout: function(l) { - //Stop processing if invalid value - if ( this.util.is_empty(l) ) { - return l; - } - //Set return type - var rtype = ( this.util.is_string(l) ) ? 'string' : null; - /* Quarantine hard-coded tags */ - - //Create DOM structure from raw template - var dom = $(l); - //Find hard-coded tag nodes - var tag_temp = new View.Template_Tag(); - var cls = tag_temp.get_class(); - var cls_new = ['x', cls].join('_'); - $(tag_temp.get_selector(), dom).each(function(idx) { - //Replace matching class name with blocking class - $(this).removeClass(cls).addClass(cls_new); - }); - //Format return value - switch ( rtype ) { - case 'string' : - dom = dom.wrap('
').parent().html(); - default : - l = dom; - } - return l; - }, - - /*-** Tags **-*/ - - /** - * Extract tags from template - * Tags are replaced with DOM element placeholders - * Extracted tags are saved as element attribute values (for future use) - * @param string l Raw layout to parse - * @return string Parsed layout - */ - parse_tags: function(l) { - //Validate - if ( !this.util.is_string(l) ) { - return ''; - } - //Parse tags in layout - //Tag regex - var re = /\{{2}\s*(\w.*?)\s*\}{2}/gim; - //Tag match results - var match; - //Iterate through template and find tags - while ( match = re.exec(l) ) { - //Replace tag in layout with DOM container - l = l.substring(0, match.index) + this.get_tag_container(match[1]) + l.substring(match.index + match[0].length); - } - return l; - }, - - /** - * Create DOM element container for tag - * @param string Tag ID (will be prefixed) - * @return string DOM element - */ - get_tag_container: function(tag) { - //Build element - var attr = this.get_tag_attribute(); - return this.util.format('', attr, escape(tag)); - }, - - get_tag_attribute: function() { - return this.get_parent().get_component_temp(View.Template_Tag).dom_get_attribute(); - }, - - /** - * Retrieve Template_Tag instance at specified index - * @param int idx (optional) Index to retrieve tag from - * @return Template_Tag Tag instance - */ - get_tag: function(idx) { - var ret = null; - if ( this.has_tags() ) { - var tags = this.get_tags(); - if ( !this.util.is_int(idx) || 0 > idx || idx >= tags.length ) { - idx = 0; - } - ret = tags[idx]; - } - return ret; - }, - - /** - * Retrieve tags from template - * Subset of tags may be retrieved based on parameter values - * Template is parsed if tags not set - * @param string name (optional) Tag type to retrieve instances of - * @param string prop (optional) Tag property to retrieve instances of - * @return array Template_Tag instances - */ - get_tags: function(name, prop) { - var a = 'tags'; - var tags = this.get_attribute(a); - //Initialize tags - if ( !this.util.is_array(tags) ) { - tags = []; - //Retrieve layout DOM tree - var d = this.dom_get(); - //Select tag nodes - var attr = this.get_tag_attribute(); - var nodes = $(d).find('[' + attr + ']'); - //Build tag instances from nodes - $(nodes).each(function(idx) { - //Get tag placeholder - var el = $(this); - var tag = new View.Template_Tag(unescape(el.attr(attr))); - //Populate valid tags - if ( tag.has_handler() ) { - //Add tag to array - tags.push(tag); - //Connect tag to DOM node - tag.dom_set(el); - //Set classes - el.addClass(tag.get_classes(' ')); - } - //Clear data attribute - el.removeAttr(attr); - }); - //Save tags - this.set_attribute(a, tags, false); - } - tags = this.get_attribute(a, [], false); - //Filter tags by parameters - if ( !this.util.is_empty(tags) && this.util.is_string(name) ) { - //Normalize - if ( !this.util.is_string(prop) ) { - prop = false; - } - var tags_filtered = []; - var tc = null; - for ( var x = 0; x < tags.length; x++ ) { - tc = tags[x]; - if ( name == tc.get_name() ) { - //Check tag property - if ( !prop || prop == tc.get_prop() ) { - tags_filtered.push(tc); - } - } - } - tags = tags_filtered; - } - return ( this.util.is_array(tags, false) ) ? tags : []; - }, - - /** - * Check if template contains tags - * @return bool TRUE if tags exist, FALSE otherwise - */ - has_tags: function() { - return ( this.get_tags().length > 0 ) ? true : false; - }, - - /*-** DOM **-*/ - - /** - * Custom DOM initialization - */ - dom_init: function() { - //Create DOM object from parsed layout - this.dom_set(this.get_layout()); - }, - - /** - * Retrieve DOM element(s) for specified tag - * @param string tag Name of tag to retrieve - * @param string prop (optional) Specific tag property to retrieve - * @return array DOM elements for tag - */ - dom_get_tag: function(tag, prop) { - var ret = $(); - var tags = this.get_tags(tag, prop); - if ( tags.length ) { - //Build selector - var level = null; - if ( this.util.is_string(tag) ) { - level = ( this.util.is_string(prop) ) ? 'full' : 'tag'; - } - var sel = '.' + tags[0].get_class(level); - ret = this.dom_get().find(sel); - } - return ret; - } -}; - -View.Template = Modeled_Component.extend(Template); - -/** - * Template tag - */ -var Template_Tag = { - /* Configuration */ - _slug: 'template_tag', - _reciprocal: true, - /* Properties */ - _attr_default: { - name: null, - prop: null, - match: null - }, - /** - * Tag Handlers - * Collection of Template_Tag_Handler instances - * @var obj - */ - handlers: {}, - /* Methods */ - - /** - * Constructor - * @param - */ - _c: function(tag_match) { - this.parse(tag_match); - }, - - /** - * Set instance attributes using tag extracted from template - * @param string tag_match Extracted tag match - */ - parse: function(tag_match) { - //Return default value for invalid instances - if ( !this.util.is_string(tag_match) ) { - return false; - } - //Parse instance options - var parts = tag_match.split('|'), - part; - if ( !parts.length ) { - return null; - } - var attrs = { - name: null, - prop: null, - match: tag_match - }; - //Get tag ID - attrs.name = parts[0]; - //Get main property - if ( attrs.name.indexOf('.') != -1 ) { - attrs.name = attrs.name.split('.', 2); - attrs.prop = attrs.name[1]; - attrs.name = attrs.name[0]; - } - //Get other attributes - for ( var x = 1; x < parts.length; x++ ) { - part = parts[x].split(':', 1); - if ( part.length > 1 && !( part[0] in attrs ) ) { - //Add key/value pair to attributes - attrs[part[0]] = part[1]; - } - } - //Save to instance - this.set_attributes(attrs, true); - }, - - /** - * Render tag output - * @param Content_Item item - * @return obj jQuery.Promise object that is resolved when tag is rendered - * Parameters passed to callbacks - * > tag obj Current tag instance - * > output string Tag output - */ - render: function(item) { - var tag = this; - return tag.get_handler().render(item, tag).pipe(function(output) { - return {'tag': tag, 'output': output}; - }); - }, - - /** - * Retrieve tag name - * @return string Tag name (DEFAULT: NULL) - */ - get_name: function() { - return this.get_attribute('name'); - }, - - /** - * Retrieve tag property - */ - get_prop: function() { - return this.get_attribute('prop'); - }, - - /** - * Retrieve tag handler - * @return Template_Tag_Handler Handler instance (Empty instance if handler does not exist) - */ - get_handler: function() { - return ( this.has_handler() ) ? this.handlers[this.get_name()] : new View.Template_Tag_Handler(''); - }, - - /** - * Check if handler exists for tag - * @return bool TRUE if handler exists, FALSE otherwise - */ - has_handler: function() { - return ( this.get_name() in this.handlers ); - }, - - /** - * Generate class names for DOM node - * @param string rtype (optional) Return data type - * > Default: array - * > If string supplied: Joined classes delimited by parameter - * @uses get_class() to generate class names - * @uses Array.join() to convert class names array to string - * @return array Class names - */ - get_classes: function(rtype) { - //Build array of class names - var cls = [ - //General tag class - this.get_class(), - //Tag name - this.get_class('tag'), - //Tag name + property - this.get_class('full') - ]; - //Convert class names array to string - if ( this.util.is_string(rtype) ) { - cls = cls.join(rtype); - } - //Return class names - return cls; - }, - - /** - * Generate DOM-compatible class name based with varied levels of specificity - * @param int level (optional) Class name specificity - * > Default: General tag class (common to all tag elements) - * > tag: Tag Name - * > full: Tag Name + Property - * @return string Class name - */ - get_class: function(level) { - var cls = ''; - switch ( level ) { - case 'tag' : - //Tag name - cls = this.add_ns(this.get_name()); - break; - case 'full' : - //Tag name + property - cls = this.add_ns([this.get_name(), this.get_prop()].join('_')); - break; - default : - //General - cls = this.get_ns(); - break; - } - return cls; - }, - - /** - * Generate tag selector based on specified class name level - * @param string level (optional) Class name specificity (@see get_class() for parameter values) - * @return string Tag selector - */ - get_selector: function(level) { - return '.' + this.get_class(level); - } -}; - -View.Template_Tag = Component.extend(Template_Tag); - -/** - * Theme tag handler - */ -var Template_Tag_Handler = { - /* Configuration */ - _slug: 'template_tag_handler', - /* Properties */ - _attr_default: { - supports_modifiers: false, - dynamic: false, - props: {} - }, - - /* Methods */ - - /** - * Render tag output - * @param Content_Item item Item currently being displayed - * @param Template_Tag Tag instance (from template) - * @return obj jQuery.Promise linked to rendering process - */ - render: function(item, instance) { - var dfr = $.Deferred(); - //Pass to attribute method - var ret = this.call_attribute('render', item, instance); - //Check for promise - if ( this.util.is_promise(ret) ) { - ret.done(function(output) { - dfr.resolve(output); - }); - } else { - //Resolve non-promises immediately - dfr.resolve(ret); - } - //Return promise - return dfr.promise(); - }, - - add_prop: function(prop, fn) { - //Get attribute - var a = 'props'; - var props = this.get_attribute(a); - //Validate - if ( !this.util.is_string(prop) || !this.util.is_func(fn) ) { - return false; - } - if ( !this.util.is_obj(props, false) ) { - props = {}; - } - //Add property - props[prop] = fn; - //Save attribute - this.set_attribute(a, props); - }, - - handle_prop: function(prop, item, instance) { - //Locate property - var props = this.get_attribute('props'); - var out = ''; - if ( this.util.is_obj(props) && ( prop in props ) && this.util.is_func(props[prop]) ) { - out = props[prop].call(this, item, instance); - } else { - out = item.get_viewer().get_label(prop); - } - return out; - } -}; - -View.Template_Tag_Handler = Component.extend(Template_Tag_Handler); -/* Update References */ - -//Attach to global object -SLB.attach('View', View); -View = SLB.View; -View.update_refs(); +/** + * View (Lightbox) functionality + * @package Simple Lightbox + * @subpackage View + * @author Archetyped + */ +/* global SLB */ +if ( jQuery ){(function ($) { + +if ( typeof SLB === 'undefined' || !SLB.attach ) { + return false; +} + +/*-** Controller **-*/ + +var View = { + + /* Properties */ + + /** + * Media item properties + * > Item key: Link URI + * > Base properties + * > id: WP Attachment ID + * > source: Source URI + * > title: Media title (generally WP attachment title) + * > desc: Media description (generally WP Attachment content) + * > type: Asset type (attachment, image, etc.) + */ + assets: {}, + + /** + * Component types that can have default instances + * @var array + */ + component_defaults: [], + + /** + * Collection of jQuery.Deferred instances added during loading routine + * @var array + */ + loading: [], + + /* Component Collections */ + + viewers: {}, + items: [], + content_handlers: {}, + groups: {}, + template_tags: {}, + + /** + * Collection/Data type mapping + * > Key: Collection name + * > Value: Data type + * @var object + */ + collections: {}, + + /** + * Temporary component instances + * For use by controller when no component instance is available + * > Key: Component slug + * > Value: Component instance + */ + component_temps: {}, + + /* Options */ + options: { + ui_animate: true, + slideshow_enabled: true, + slideshow_autostart: false, + slideshow_duration: '6' + }, + + /* Methods */ + + /* Init */ + + update_refs: function() { + var c; + var r; + var ref; + for ( var p in this ) { + if ( !this.util.is_func(this[p]) || !( '_refs' in this[p].prototype ) ) { + continue; + } + //Set component + c = this[p]; + if ( !this.util.is_empty(c.prototype._refs) ) { + for ( r in c.prototype._refs ) { + ref = c.prototype._refs[r]; + if ( this.util.is_func(ref) ) { + continue; + } + if ( this.util.is_string(ref) && ref in this ) { + ref = c.prototype._refs[r] = this[ref]; + } + if ( !this.util.is_func(ref) ) { + delete c.prototype_refs[r]; + } + } + } + } + + /* Initialize components */ + this.init_components(); + }, + + /** + * Initialization + */ + init: function(options) { + var t = this; + $.when.apply($, this.loading).always(function() { + //Set options + $.extend(true, t.options, options); + //History + $(window).on('popstate', function(e) { + var state = e.originalEvent.state; + if ( t.util.in_obj(state, ['item', 'viewer']) ) { + var v = t.get_viewer(state.viewer); + v.history_handle(e); + return e.preventDefault(); + } + }); + + /* Set defaults */ + + //Items + t.init_items(); + }); + }, + + init_components: function() { + this.collections = { + 'viewers': this.Viewer, + 'items': this.Content_Item, + 'content_handlers': this.Content_Handler, + 'groups': this.Group, + 'themes': this.Theme, + 'template_tags': this.Template_Tag + }; + + this.component_defaults = [ + this.Viewer, + ]; + + }, + + /* Components */ + + component_make_default: function(type) { + var ret = false; + for ( var x = 0; x < this.component_defaults.length; x++ ) { + if ( type === this.component_defaults[x] ) { + ret = true; + break; + } + } + return ret; + }, + + /** + * Validates component type + * @param function comp Component type to check + * @return bool TRUE if param is valid component, FALSE otherwise + */ + check_component: function(comp) { + //Validate component type + return ( this.util.is_func(comp) && ('_slug' in comp.prototype ) && ( comp.prototype instanceof (this.Component) ) ) ? true : false; + }, + + /** + * Retrieve collection of components of specified type + * @param function type Component type + * @return object|array|null Component collection (NULL if invalid) + */ + get_components: function(type) { + var ret = null; + if ( this.util.is_func(type) ) { + //Determine collection + for ( var coll in this.collections ) { + if ( type === this.collections[coll] && coll in this ) { + ret = this[coll]; + break; + } + } + } + return ret; + }, + + /** + * Retrieve component from specific collection + * @param function type Component type + * @param string id Component ID + * @return object|null Component reference (NULL if invalid) + */ + get_component: function(type, id) { + var ret = null; + //Validate parameters + if ( !this.util.is_func(type) ) { + return ret; + } + //Sanitize id + if ( !this.util.is_string(id) ) { + id = null; + } + + //Get component from collection + var coll = this.get_components(type); + if ( this.util.is_obj(coll) ) { + var tid = ( this.util.is_string(id) ) ? id : this.util.add_prefix('default'); + if ( tid in coll ) { + ret = coll[tid]; + } + } + + //Default: Create default component + if ( this.util.is_empty(ret) ) { + if ( this.util.is_string(id) || this.component_make_default(type) ) { + ret = this.add_component(type, id); + } + } + //Return component + return ret; + }, + + /** + * Create new component instance and save to appropriate collection + * @param function type Component type to create + * @param string id ID of component + * @param object options Component initialization options (Default options used if default component is allowed) + * @return object|null New component (NULL if invalid) + */ + add_component: function(type, id, options) { + //Validate type + if ( !this.util.is_func(type) ) { + return false; + } + //Validate request + if ( this.util.is_empty(id) && !this.component_make_default(type) ) { + return false; + } + //Defaults + var ret = null; + if ( this.util.is_empty(id) ) { + id = this.util.add_prefix('default'); + } + if ( !this.util.is_obj(options) ) { + options = {}; + } + //Check if specialized method exists for component type + var m = ( 'component' !== type.prototype._slug ) ? 'add_' + type.prototype._slug : null; + if ( !this.util.is_empty(m) && ( m in this ) && this.util.is_func(this[m]) ) { + ret = this[m](id, options); + } + //Default process + else { + ret = new type(id, options); + } + + //Add new component to collection + if ( this.util.is_type(ret, type) ) { + //Get collection + var coll = this.get_components(type); + //Add to collection + switch ( $.type(coll) ) { + case 'object' : + coll[id] = ret; + break; + case 'array' : + coll.push(ret); + break; + } + } else { + ret = null; + } + //Return new component + return ret; + }, + + /** + * Create new temporary component instance + * @param function type Component type + * @return New temporary instance + */ + add_component_temp: function(type) { + var ret = null; + if ( this.check_component(type) ) { + //Create new instance + ret = new type(''); + //Save to collection + this.component_temps[ret._slug] = ret; + } + return ret; + }, + + /** + * Retrieve temporary component instance + * Creates new temp component instance for type if not previously created + * @param function type Component type to retrieve temp instance for + * @return obj Temporary component instance + */ + get_component_temp: function(type) { + return ( this.has_component_temp(type) ) ? this.component_temps[type.prototype._slug] : this.add_component_temp(type); + }, + + /** + * Check if temporary component instance exists + * @param function type Component type to check for + * @return bool TRUE if temp instance exists, FALSE otherwise + */ + has_component_temp: function(type) { + return ( this.check_component(type) && ( type.prototype._slug in this.component_temps ) ) ? true : false; + }, + + /* Properties */ + + /** + * Retrieve specified options + * @param array opts Array of option names + * @return object Specified options (Default: empty object) + */ + get_options: function(opts) { + var ret = {}; + //Validate + if ( this.util.is_string(opts) ) { + opts = [opts]; + } + if ( !this.util.is_array(opts) ) { + return ret; + } + //Get specified options + for ( var x = 0; x < opts.length; x++ ) { + //Skip if option not set + if ( !( opts[x] in this.options ) ) { + continue; + } + ret[ opts[x] ] = this.options[ opts[x] ]; + } + return ret; + }, + + /** + * Retrieve option + * @uses View.options + * @param string opt Option to retrieve + * @param mixed def (optional) Default value if option does not exist (Default: NULL) + * @return mixed Option value + */ + get_option: function(opt, def) { + var ret = this.get_options(opt); + if ( this.util.is_obj(ret) && ( opt in ret ) ) { + ret = ret[opt]; + } else { + ret = ( this.util.is_set(def) ) ? def : null; + } + return ret; + }, + + /* Viewers */ + + /** + * Add viewer instance to collection + * @param string id Viewer ID + * @param obj options Viewer options + */ + add_viewer: function(id, options) { + //Validate + if ( !this.util.is_string(id) ) { + return false; + } + if ( !this.util.is_obj(options, false) ) { + options = {}; + } + //Create viewer + var v = new this.Viewer(id, options); + //Add to collection + this.viewers[v.get_id()] = v; + //Return viewer + return v; + }, + + /** + * Retrieve all viewer instances + * @return obj Viewer instances + */ + get_viewers: function() { + return this.viewers; + }, + + /** + * Check if viewer exists + * @param string v Viewer ID + * @return bool TRUE if viewer exists, FALSE otherwise + */ + has_viewer: function(v) { + return ( this.util.is_string(v) && v in this.get_viewers() ) ? true : false; + }, + + /** + * Retrieve Viewer instance + * Default viewer retrieved if specified viewer does not exist + * > Default viewer created if necessary + * @param string v Viewer ID to retrieve + * @return Viewer Viewer instance + */ + get_viewer: function(v) { + //Retrieve default viewer if specified viewer not set + if ( !this.has_viewer(v) ) { + v = this.util.add_prefix('default'); + //Create default viewer if necessary + if ( !this.has_viewer(v) ) { + this.add_viewer(v); + } + } + return this.get_viewers()[v]; + }, + + /* Items */ + + /** + * Set event handlers + */ + init_items: function() { + //Define handler + var t = this; + var handler = function() { + var ret = t.show_item(this); + if ( !t.util.is_bool(ret) ) { + ret = true; + } + return !ret; + }; + + //Get activated links + var sel = this.util.format('a[href][%s="%s"]', this.util.get_attribute('active'), 1); + //Add event handler + $(document).on('click', sel, handler); + }, + + get_items: function() { + return this.get_components(this.Content_Item); + }, + + /** + * Retrieve specific Content_Item instance + * @param mixed Item reference + * > Content_Item: Item instance (returned immediately) + * > DOM element: DOM element to get item for + * > int: Index of cached item + * @return Content_Item Item instance for DOM node + */ + get_item: function(ref) { + //Evaluate reference type + + //Content Item instance + if ( this.util.is_type(ref, this.Content_Item) ) { + return ref; + } + //Retrieve item instance + var item = null; + + //DOM element + if ( this.util.in_obj(ref, 'nodeType') ) { + //Check if item instance attached to element + var key = this.get_component_temp(this.Content_Item).get_data_key(); + item = $(ref).data(key); + } + //Cached item (index) + else if ( this.util.is_int(ref, false) ) { + var items = this.get_items(); + if ( items.length > ref ) { + item = items[ref]; + } + } + //Create default item instance + if ( !this.util.is_type(item, this.Content_Item) ) { + item = this.add_item(ref); + } + return item; + }, + + /** + * Create new item instance + * @param obj el DOM element representing item + * @return Content_Item New item instance + */ + add_item: function(el) { + var item = new this.Content_Item(el); + return item; + }, + + /** + * Display item in viewer + * @param obj el DOM element representing item + */ + show_item: function(el) { + var ret = this.get_item(el).show(); + return ret; + }, + + /** + * Cache item instance + * @uses this.items to store cached items + * @param Content_Item item Item to cache + * @return int Index of item in cache + */ + save_item: function(item) { + var ret = -1; + if ( !this.util.is_type(item, this.Content_Item) ) { + return ret; + } + var items = this.get_items(); + //Check if item exists in collection + ret = $.inArray(item, items); + //Cache item + if ( -1 === ret ) { + ret = items.push(item) - 1; + } + //Return item index in cache + return ret; + }, + + /* Content Handler */ + + get_content_handlers: function() { + return this.get_components(this.Content_Handler); + }, + + /** + * Find matching content handler for item + * @param Content_Item|string item Item to find handler for (or ID of Handler) + * @return Content_Handler|null Matching content handler (NULL if no matching handler found) + */ + get_content_handler: function(item) { + //Determine handler to retrieve + var type = ( this.util.is_type(item, this.Content_Item) ) ? item.get_attribute('type', '') : item.toString(); + //Retrieve handler + var types = this.get_content_handlers(); + return ( type in types ) ? types[type] : null; + }, + + /** + * Add/Update Content Handler + * @param string id Handler ID + * @param obj attr Handler attributes + * @return obj|bool Handler instance (FALSE on failure) + */ + extend_content_handler: function(id, attr) { + var hdl = false; + if ( !this.util.is_string(id) || !this.util.is_obj(attr) ) { + return hdl; + } + hdl = this.get_content_handler(id); + //Add new content handler + if ( null == hdl ) { + var hdls = this.get_content_handlers(); + hdls[id] = hdl = new this.Content_Handler(id, attr); + } + //Update existing handler + else { + hdl.set_attributes(attr); + } + //Load styles + if ( this.util.in_obj(attr, 'styles') ) { + this.load_styles(attr.styles); + } + return hdl; + }, + + /* Group */ + + /** + * Add new group + * @param string g Group ID + * > If group with same ID already set, new group replaces existing one + * @param object attrs (optional) Group attributes + */ + add_group: function(g, attrs) { + //Create new group + g = new this.Group(g, attrs); + //Add group to collection + if ( this.util.is_string(g.get_id()) ) { + this.groups[g.get_id()] = g; + } + }, + + /** + * Retrieve groups + * @uses groups property + * @return object Registered groups + */ + get_groups: function() { + return this.groups; + }, + + /** + * Retrieve specified group + * @param string g Group ID + * @return object|null Group instance (NULL if group does not exist) + */ + get_group: function(g) { + if ( this.util.is_string(g) ) { + if ( !this.has_group(g) ) { + //Add new group (if necessary) + this.add_group(g); + } + //Retrieve group + g = this.get_groups()[g]; + } + return ( this.util.is_type(g, this.Group) ) ? g : null; + }, + + /** + * Checks if group is registered + * @uses get_groups() to retrieve registered groups + * @return bool TRUE if group exists, FALSE otherwise + */ + has_group: function(g) { + return ( this.util.is_string(g) && ( g in this.get_groups() ) ) ? true : false; + }, + + /* Theme */ + + /** + * Add/Update theme + * @param string name Theme name + * @param obj attr Theme options + * > Multiple attribute parameters are merged + * @return obj Theme model + */ + extend_theme: function(id, attr) { + //Validate + if ( !this.util.is_string(id) ) { + return false; + } + var dfr = $.Deferred(); + this.loading.push(dfr); + + //Get model if it already exists + var model = this.get_theme_model(id); + + //Create default attributes for new theme + if ( this.util.is_empty(model) ) { + //Default + model = {'parent': null, 'id': id}; + //Save theme model + this.Theme.prototype._models[id] = model; + } + + //Add custom attributes + if ( this.util.is_obj(attr) ) { + //Sanitize + if ( 'id' in attr ) { + delete(attr['id']); + } + $.extend(model, attr); + } + + //Load styles + if ( this.util.in_obj(attr, 'styles') ) { + this.load_styles(attr.styles); + } + + //Link parent model + if ( this.util.is_string(model.parent) ) { + model.parent = this.get_theme_model(model.parent); + } + + //Complete loading when all components loaded + dfr.resolve(); + return model; + }, + + /** + * Retrieve theme models + * @return obj Theme models + */ + get_theme_models: function() { + //Retrieve matching theme model + return this.Theme.prototype._models; + }, + + /** + * Retrieve theme model + * @param string id Theme to retrieve + * @return obj Theme model (Default: empty object) + */ + get_theme_model: function(id) { + var ms = this.get_theme_models(); + return ( this.util.in_obj(ms, id) ) ? ms[id] : {}; + }, + + /** + * Add/Update Template Tag Handler + * @param string id Handler ID + * @param obj attr Handler attributes + * @return obj|bool Handler instance (FALSE on failure) + */ + extend_template_tag_handler: function(id, attr) { + var hdl = false; + if ( !this.util.is_string(id) || !this.util.is_obj(attr) ) { + return hdl; + } + var hdls = this.get_template_tag_handlers(); + //Add new content handler + if ( !this.util.in_obj(hdls, id) ) { + hdls[id] = hdl = new this.Template_Tag_Handler(id, attr); + } + //Update existing handler + else { + hdl = hdls[id]; + hdl.set_attributes(attr); + } + //Load styles + if ( this.util.in_obj(attr, 'styles') ) { + this.load_styles(attr.styles); + } + return hdl; + }, + + /** + * Retrieve Template Tag Handler collection + * @return obj Template_Tag_Handler objects + */ + get_template_tag_handlers: function() { + return this.Template_Tag.prototype.handlers; + }, + + /** + * Retrieve template tag handler + * @param string id ID of tag handler to retrieve + * @return Template_Tag_Handler Tag Handler instance (new instance for invalid ID) + */ + get_template_tag_handler: function(id) { + var handlers = this.get_template_tag_handlers(); + //Retrieve existing handler + if ( this.util.is_string(id) && ( id in handlers ) ) { + return handlers[id]; + } + //Default: Return empty handler + return new this.Template_Tag_Handler(id, {}); + }, + + /** + * Load styles + * @param array styles Styles to load + */ + load_styles: function(styles) { + if ( this.util.is_array(styles) ) { + var out = ''; + $.each(styles, function(i, style) { + out += ''; + }); + $('head').append(out); + } + } +}; + +/* Components */ +var Component = { + /*-** Properties **-*/ + + /* Internal/Configuration */ + + /** + * Base name of component type + * @var string + */ + _slug: 'component', + + /** + * Valid component references for current component + * > Key (string): Property name that stores reference + * > Value (function): Data type of component + * @var object + */ + _refs: {}, + + /** + * Components that may contain current object + * Used for retrieving data from a parent object + * Example: An Item may be contained by a Group + * > Value (strong): Property name of container component + * @var array + */ + _containers: [], + + /** + * Whether DOM element and component are connected in 1:1 relationship + * Some components will be assigned to different DOM elements depending on usage + * @var bool + */ + _reciprocal: false, + + /** + * DOM Element tied to component + * @var DOM Element + */ + _dom: null, + + /** + * Default attributes + * @var object + */ + _attr_default: {}, + + /** + * Attributes passed to constructor + * @var obj + */ + _attr_init: null, + + /** + * Attributes to retrieve from parent (controller) + * @var array + */ + _attr_parent: [], + + /** + * Defines how parent properties should be remapped to component properties + * @var object + */ + _attr_map: {}, + + /** + * Event handlers + * @var object + * > Key: string Event type + * > Value: array Handlers + */ + _events: null, + + /** + * Status management + * @var object + * > Key: Status ID + * > Value: Status value + */ + _status: null, + + /* Public */ + + attributes: false, + + /** + * Component ID + * @var string + */ + id: '', + + /* Init */ + + _c: function(id, attributes) { + //Set ID + this.set_id(id); + //Save init attributes + this._attr_init = attributes; + this.register_hooks(); + }, + + _set_parent: function() { + this._parent = View; + this.util._parent = this; + }, + + /** + * Register hooks on init + * Placeholder method to be overridden by child classes + */ + register_hooks: function() {}, + + /* Methods */ + + /* Properties */ + + /** + * Retrieve status + * @param string id Status to retrieve + * @param bool raw (optional) Retrieve raw value (Default: FALSE) + * @return mixed Status value (Default: bool) + */ + get_status: function(id, raw) { + var ret = false; + if ( this.util.in_obj(this._status, id) ) { + ret = ( !!raw ) ? this._status[id] : !!this._status[id]; + } + return ret; + }, + + /** + * Set status + * @param string id Status to retrieve + * @param mixed val Status value (Default: TRUE) + * @return mixed Status value (Default: bool) + */ + set_status: function(id, val) { + //Validate + if ( this.util.is_string(id) ) { + if ( !this.util.is_set(val) ) { + val = true; + } + //Initialize property + if ( !this.util.is_obj(this._status, false) ) { + this._status = {}; + } + //Set status + this._status[id] = val; + } else if ( !this.util.is_set(val) ) { + val = false; + } + return val; + }, + + /** + * Retrieve instance ID + * @uses id as ID base + * @uses _slug to add namespace (if necessary) + * @param bool ns (optional) Whether or not to namespace ID (Default: FALSE) + * @return string Instance ID + */ + get_id: function(ns) { + //Validate + if ( !this.check_id() ) { + this.id = ''; + } + var id = this.id; + //Namespace ID + if ( this.util.is_bool(ns) && ns ) { + id = this.add_ns(id); + } + + return id; + }, + + /** + * Set instance ID + * @param string id Unique ID + */ + set_id: function(id) { + this.id = ( this.check_id(id) ) ? id : ''; + }, + + /** + * Validate ID value + * @param string id (optional) ID value (Default: Component ID) + * @param bool nonempty (optional) TRUE if it should also check for empty strings, FALSE otherwise (Default: FALSE) + * @return bool TRUE if ID is valid, FALSE otherwise + */ + check_id: function(id, nonempty) { + //Validate + if ( arguments.length === 1 && this.util.is_bool(arguments[0]) ) { + nonempty = arguments[0]; + id = null; + } + if ( this.util.is_empty(id) ) { + id = this.id; + } + if ( !this.util.is_bool(nonempty) ) { + nonempty = false; + } + return ( this.util.is_string(id, nonempty) ) ? true : false; + }, + + /** + * Get namespace + * @uses _slug for namespace segment + * @uses Util.add_prefix() to prefix slug + * @return string Component namespace + */ + get_ns: function() { + return this.util.add_prefix(this._slug); + }, + + /** + * Add namespace to value + * @param string val Value to namespace + * @return string Namespaced value (Empty string if invalid value provided) + */ + add_ns: function(val) { + return ( this.util.is_string(val) ) ? this.get_ns() + '_' + val : ''; + }, + + /* Components */ + + /** + * Retrieve component containers + * @uses _container property + * @return array Component containers + */ + get_containers: function() { + //Sanitize property + if ( !this.util.is_array(this._containers) ) { + this._containers = []; + } + //Return value + return this._containers; + }, + + /** + * Check if current object has potential container objects + * @return bool TRUE if containers exist, FALSE otherwise + */ + has_containers: function() { + return ( this.get_containers().length > 0 ); + }, + + /** + * Check if reference exists in object + * @param string ref Reference ID + * @return bool TRUE if reference exists, FALSE otherwise + */ + has_reference: function(ref) { + return ( this.util.is_string(ref) && ( ref in this ) && ( ref in this.get_references() ) ) ? true : false; + }, + + /** + * Retrieve object references + * @uses _refs + * @return obj References object + + */ + get_references: function() { + return this._refs; + }, + + /** + * Retrieve reference data type + * @param string ref Reference ID + * @return function Reference data type (NULL if invalid) + */ + get_reference: function(ref) { + return ( this.has_reference(ref) ) ? this._refs[ref] : null; + }, + + /** + * Checks if component is valid + * @param obj|string Component instance or ID + * > If ID is specified then it will check for component on current instance + * @param function|string ctype Component type + * > If component is an object, then ctype is required + * > If component is string ID, then ctype is optional (Default: reference type) + * > If ctype is a function, then it is compared to component directly + * > If ctype is a string, then the component reference type is retrieved + * @uses get_reference() + * @return bool TRUE if component is valid, FALSE otherwise + */ + check_component: function(comp, ctype) { + //Validate + if ( this.util.is_empty(comp) || + ( this.util.is_obj(comp) && !this.util.is_func(ctype) ) || + ( this.util.is_string(comp) && !this.has_reference(comp) ) || + ( this.util.is_empty(ctype) && !this.util.is_string(comp) ) || + ( !this.util.is_obj(comp) && !this.util.is_string(comp) ) + ) { + return false; + } + //Get component type + if ( !this.util.is_func(ctype) ) { + //Component is a string ID + ctype = this.get_reference(comp); + } + //Get component instance + if ( this.util.is_string(comp) ) { + comp = this.get_component(comp, false); + } + return this.util.is_type(comp, ctype); + }, + + /** + * Retrieve component reference from current object + * > Procedure: + * > Check if property already set + * > Check attributes + * > Check container object(s) + * > Check parent object (controller) + * @uses _containers to check potential container components for references + * @param string cname Component name + * @param bool check_attr (optional) Whether or not to check instance attributes for component (Default: TRUE) + * @param bool get_default (optional) Whether or not to retrieve default object from controller if none exists in current instance (Default: TRUE) + * @param bool recursive (optional) Whether or not to check containers for specified component reference (Default: TRUE) + * @return object|null Component reference (NULL if no component found) + */ + get_component: function(cname, check_attr, get_default, recursive) { + var c = null; + //Validate request + if ( !this.util.is_string(cname) || !( cname in this ) || !this.has_reference(cname) ) { + return c; + } + + //Normalize parameters + if ( !this.util.is_bool(check_attr) ) { + check_attr = true; + } + if ( !this.util.is_bool(get_default) ) { + get_default = true; + } + if ( !this.util.is_bool(recursive) ) { + recursive = true; + } + var ctype = this._refs[cname]; + //Phase 1: Check if component reference previously set + if ( this.util.is_type(this[cname], ctype) ) { + return this[cname]; + } + //If reference not set, iterate through component hierarchy until reference is found + c = this[cname] = null; + + //Phase 2: Check attributes + if ( check_attr ) { + c = this.get_attribute(cname); + //Save object-specific component reference + if ( !this.util.is_empty(c) ) { + c = this.set_component(cname, c); + } + } + + //Phase 3: Check Container(s) + if ( recursive && this.util.is_empty(c) && this.has_containers() ) { + var containers = this.get_containers(); + var con = null; + for ( var i = 0; i < containers.length; i++ ) { + con = containers[i]; + //Validate container + if ( con === cname ) { + continue; + } + //Retrieve container + con = this.get_component(con, true, false); + if ( this.util.is_empty(con) ) { + continue; + } + //Attempt to retrieve component from container + c = con.get_component(cname); + //Stop iterating if valid component found + if ( !this.util.is_empty(c) ) { + break; + } + } + } + + //Phase 4: From controller (optional) + if ( get_default && this.util.is_empty(c) ) { + c = this.get_parent().get_component(ctype); + } + return c; + }, + + /** + * Sets component reference on current object + * > Component property reset (set to NULL) if invalid component supplied + * @param string name Name of property to set component on object + * @param string|object ref Component or Component ID (to be retrieved from controller) + * @param function validate (optional) Additional validation to be performed (Must return bool: TRUE (Valid)/FALSE (Invalid)) + * @return object Component (NULL if invalid) + */ + set_component: function(name, ref, validate) { + var clear = null; + //Make sure component property exists + if ( !this.has_reference(name) ) { + return clear; + } + //Normalize reference + if ( this.util.is_empty(ref) ) { + ref = clear; + } + var ctype = this.get_reference(name); + + //Get component from controller if ID supplied + if ( this.util.is_string(ref) ) { + ref = this.get_parent().get_component(ctype, ref); + } + + if ( !this.util.is_type(ref, ctype) ) { + ref = clear; + } + + //Additional validation + if ( !this.util.is_empty(ref) && this.util.is_func(validate) && !validate.call(this, ref) ) { + ref = clear; + } + //Set (or clear) component reference + this[name] = ref; + //Return value for confirmation + return this[name]; + }, + + /* Attributes */ + + /** + * Initializes attributes + */ + init_attributes: function(force) { + if ( !this.util.is_bool(force) ) { + force = false; + } + if ( force || !this.util.is_obj(this.attributes) ) { + this.attributes = {}; + //Build attribute groups + var attrs = [{}]; + attrs.push(this.init_default_attributes()); + if ( this.dom_has() ) { + attrs.push(this.get_dom_attributes()); + } + if ( this.util.is_obj(this._attr_init) ) { + attrs.push(this._attr_init); + } + //Merge attributes + this.attributes = $.extend.apply(null, attrs); + } + }, + + /** + * Generate default attributes for component + * @uses _attr_parent to determine options to retrieve from controller + * @uses View.get_options() to get values from controller + * @uses _attr_map to Remap controller attributes to instance attributes + * @uses _attr_default to Store default attributes + */ + init_default_attributes: function() { + //Get parent options + var opts = this.get_parent().get_options(this._attr_parent); + if ( this.util.is_obj(opts) ) { + //Remap + for ( var opt in this._attr_map ) { + if ( opt in opts ) { + //Move value to new property + opts[this._attr_map[opt]] = opts[opt]; + //Delete old property + delete opts[opt]; + } + } + //Merge with default attributes + $.extend(true, this._attr_default, opts); + } + return this._attr_default; + }, + + /** + * Retrieve DOM attributes + */ + get_dom_attributes: function() { + var attrs = {}; + var el = this.dom_get(); + if ( el.length ) { + //Get attributes from element + var opts = $(el).get(0).attributes; + if ( this.util.is_obj(opts) ) { + var attr_prefix = this.util.get_attribute(); + $.each(opts, function(idx, opt) { + if ( opt.name.indexOf( attr_prefix ) === -1 ) { + return true; + } + //Process custom attributes + //Strip prefix + var key = opt.name.substr(attr_prefix.length + 1); + attrs[key] = opt.value; + }); + } + } + return attrs; + }, + + /** + * Retrieve all instance attributes + * @uses parse_attributes() to initialize attributes (if necessary) + * @uses attributes + * @return obj Attributes + */ + get_attributes: function() { + //Initilize attributes + this.init_attributes(); + //Return attributes + return this.attributes; + }, + + /** + * Retrieve value of specified attribute for value + * @param string key Attribute to retrieve + * @param mixed def (optional) Default value if attribute is not set + * @param bool enforce_type (optional) Whether data type should match default value (Default: TRUE) + * > If possible, attribute value will be converted to match default data type + * > If attribute value cannot match default data type, default value will be used + * @return mixed Attribute value (NULL if attribute is not set) + */ + get_attribute: function(key, def, enforce_type) { + //Validate + if ( !this.util.is_set(def) ) { + def = null; + } + if ( !this.util.is_string(key) ) { + return def; + } + if ( !this.util.is_bool(enforce_type) ) { + enforce_type = true; + } + //Get attribute value + var ret = ( this.has_attribute(key) ) ? this.get_attributes()[key] : def; + //Validate type + if ( enforce_type && ret !== def && null !== def && !this.util.is_type(ret, $.type(def), false) ) { + //Convert type + //Scalar default + if ( this.util.is_scalar(def, false) ) { + //Non-scalar attribute + if ( !this.util.is_scalar(ret, false) ) { + ret = def; + } else if ( this.util.is_string(def, false) ) { + ret = ret.toString(); + } else if ( this.util.is_num(def, false) && !this.util.is_num(ret, false) ) { + ret = ( this.util.is_int(def, false) ) ? parseInt(ret) : parseFloat(ret); + if ( !this.util.is_num(ret, false) ) { + ret = def; + } + } else if ( this.util.is_bool(def, false) ) { + ret = ( this.util.is_string(ret) || ( this.util.is_num(ret) ) ); + } else { + ret = def; + } + } + //Non-scalar default + else { + ret = def; + } + } + return ret; + }, + + /** + * Call attribute as method + * @param string attr Attribute to call + * @param arguments (optional) Additional arguments to pass to method + */ + call_attribute: function(attr, args) { + attr = this.get_attribute(attr); + if ( this.util.is_func(attr) ) { + //Get arguments + args = Array.prototype.slice.call(arguments, 1); + //Pass arguments to user-defined method + attr = attr.apply(this, args); + } + return attr; + }, + + /** + * Check if attribute exists + * @param string key Attribute name + * @return bool TRUE if exists, FALSE otherwise + */ + has_attribute: function(key) { + return ( key in this.get_attributes() ); + }, + + /** + * Set component attributes + * @param obj attributes Attributes to set + * @param bool full (optional) Whether to fully replace or merge component's attributes with new values (Default: Merge) + */ + set_attributes: function(attributes, full) { + if ( !this.util.is_bool(full) ) { + full = false; + } + + //Initialize attributes + this.init_attributes(full); + + //Merge new/existing attributes + if ( this.util.is_obj(attributes) ) { + $.extend(this.attributes, attributes); + } + }, + + /** + * Set value for a component attribute + * @uses get_attributes() to retrieve attributes + * @param string key Attribute to set + * @param mixed val Attribute value + */ + set_attribute: function(key, val) { + if ( this.util.is_string(key) && this.util.is_set(val) ) { + this.get_attributes()[key] = val; + } + return val; + }, + + /* DOM */ + + /** + * Generate selector for retrieving child element + * @param string element Class name of child element + * @return string Element selector + */ + dom_get_selector: function(element) { + return ( this.util.is_string(element) ) ? '.' + this.add_ns(element) : ''; + }, + + dom_get_attribute: function() { + return this.util.get_attribute(this._slug); + }, + + /** + * Set reference of instance on DOM element + * @uses _reciprocal to determine if DOM element should also be attached to instance + * @param string|obj (jQuery) el DOM element to attach instance to + * @return jQuery DOM element set + */ + dom_set: function(el) { + el = $(el); + //Save instance to DOM object + el.data(this.get_data_key(), this); + //Save DOM object to instance + if ( this._reciprocal ) { + this._dom = el; + } + return el; + }, + + /** + * Retrieve attached DOM element + * @uses _dom to retrieve attached DOM element + * @uses dom_put() to insert child element + * @param string element Child element to retrieve + * @param bool put (optional) Whether to insert element if it does not exist (Default: FALSE) + * @param obj options (optional) Options for creating new object + * @return obj jQuery DOM element + */ + dom_get: function(element, put, options) { + //Init Component DOM + if ( !this.get_status('dom_init') ) { + this.set_status('dom_init'); + this.dom_init(); + } + //Check for main DOM element + var ret = this._dom; + if ( !!ret && this.util.is_string(element) ) { + var ch = $(ret).find( this.dom_get_selector(element) ); + //Check for child element + if ( ch.length ) { + ret = ch; + } else if ( this.util.is_bool(put) && put ) { + //Insert child element + ret = this.dom_put(element, options); + } + } + return $(ret); + }, + + /** + * Initialize DOM element + * To be overridden by child classes + */ + dom_init: function() {}, + + /** + * Wrap output in DOM element + * Wrapper element created and added to main DOM element if not yet created + * @param string element ID for DOM element (Used as class name for wrapper) + * @param string|jQuery|obj content Content to add to DOM (Object contains element properties) + * > tag : Element tag name + * > content : Element content + * @return jQuery Inserted element(s) + */ + dom_put: function(element, content) { + var r = null; + //Stop processing if main DOM element not set or element is not valid + if ( !this.dom_has() || !this.util.is_string(element) ) { + return $(r); + } + //Setup options + var strip = ['tag', 'content', 'put_success']; + var options = { + 'tag': 'div', + 'content': '', + 'class': this.add_ns(element) + }; + //Setup content + if ( !this.util.is_empty(content) ) { + if ( this.util.is_type(content, jQuery, false) || this.util.is_string(content, false) ) { + options.content = content; + } + else if ( this.util.is_obj(content, false) ) { + $.extend(options, content); + } + } + var attrs = $.extend({}, options); + for ( var x = 0; x < strip.length; x++ ) { + delete attrs[strip[x]]; + } + //Retrieve existing element + var d = this.dom_get(); + r = $(this.dom_get_selector(element), d); + //Create element (if necessary) + if ( !r.length ) { + r = $(this.util.format('<%s />', options.tag), attrs).appendTo(d); + if ( r.length && this.util.is_method(options, 'put_success') ) { + options['put_success'].call(r, r); + } + } + //Set content + $(r).append(options.content); + return $(r); + }, + + /** + * Check if DOM element is set for instance + * DOM is initialized before evaluation + * @return bool TRUE if DOM element set, FALSE otherwise + */ + dom_has: function() { + return ( !!this.dom_get().length ); + }, + + /* Data */ + + /** + * Retrieve key used to store data in DOM element + * @return string Data key + */ + get_data_key: function() { + return this.get_ns(); + }, + + /* Events */ + + /** + * Register event handler for custom event + * Structure + * > Events (obj) + * > Event-Name (array) + * > Handlers (functions) + * @param mixed event Custom event to register handler for + * > string: Standard event handler + * > array: Multiple events to register single handler on + * > object: Map of events/handlers + * @param function fn Event handler + * @param obj options Handler registration options + * > clear (bool) Clear existing event handlers before setting current handler (Default: FALSE) + * @return obj Component instance (allows chaining) + */ + on: function(event, fn, options) { + //Handle request types + if ( !this.util.is_string(event) || !this.util.is_func(fn) ) { + var t = this; + var args = Array.prototype.slice.call(arguments, 1); + if ( this.util.is_array(event) ) { + //Events array + $.each(event, function(idx, val) { + t.on.apply(t, [val].concat(args)); + }); + } else if ( this.util.is_obj(event) ) { + //Events map + $.each(event, function(ev, hdl) { + t.on.apply(t, [ev, hdl].concat(args)); + }); + } + return this; + } + + //Options + + //Default options + var options_std = { + clear: false + }; + if ( !this.util.is_obj(options, false) ) { + //Reset options + options = {}; + } + //Build options + options = $.extend({}, options_std, options); + //Initialize events bucket + if ( !this.util.is_obj(this._events, false) ) { + this._events = {}; + } + //Setup event + var es = this._events; + if ( !( event in es ) || !this.util.is_obj(es[event], false) || !!options.clear ) { + es[event] = []; + } + //Add event handler + es[event].push(fn); + return this; + }, + + /** + * Trigger custom event + * Event handlers are executed in the context of the current component instance + * Event handlers are passed parameters + * > ev (obj) Event object + * > type (string) Event name + * > data (mixed) Data to pass to handlers (if supplied) + * > component (obj) Current component instance + * @param string event Custom event to trigger + * @param mixed data (optional) Data to pass to event handlers + * @return jQuery.Promise Promise that is resolved once event handlers are resolved + */ + trigger: function(event, data) { + var dfr = $.Deferred(); + var dfrs = []; + var t = this; + //Handle array of events + if ( this.util.is_array(event) ) { + $.each(event, function(idx, val) { + //Collect promises from triggered events + dfrs.push( t.trigger(val, data) ); + }); + //Resolve trigger when all events have been resolved + $.when.apply(t, dfrs).done(function() { + dfr.resolve(); + }); + return dfr.promise(); + } + //Validate + if ( !this.util.is_string(event) || !( event in this._events ) ) { + dfr.resolve(); + return dfr.promise(); + } + //Create event object + var ev = { 'type': event, 'data': null }; + //Add data to event object + if ( this.util.is_set(data) ) { + ev.data = data; + } + //Fire handlers for event + $.each(this._events[event], function(idx, fn) { + //Call handler (`this` set to current instance) + //Collect promises from event handlers + dfrs.push( fn.call(t, ev, t) ); + }); + //Resolve trigger when all handlers have been resolved + $.when.apply(this, dfrs).done(function() { + dfr.resolve(); + }); + return dfr.promise(); + } +}; + +View.Component = Component = SLB.Class.extend(Component); + +/** + * Content viewer + * @param obj options Init options + */ +var Viewer = { + + /* Configuration */ + + _slug: 'viewer', + + _refs: { + item: 'Content_Item', + theme: 'Theme' + }, + + _reciprocal: true, + + _attr_default: { + loop: true, + animate: true, + autofit: true, + overlay_enabled: true, + overlay_opacity: '0.8', + container: null, + slideshow_enabled: true, + slideshow_autostart: true, + slideshow_duration: 2, + slideshow_active: false, + slideshow_timer: null, + labels: { + close: 'close', + nav_prev: '« prev', + nav_next: 'next »', + slideshow_start: 'start slideshow', + slideshow_stop: 'stop slideshow', + group_status: 'Image %current% of %total%', + loading: 'loading' + } + }, + + _attr_parent: [ + 'theme', + 'group_loop', + 'ui_autofit', 'ui_animate', 'ui_overlay_opacity', 'ui_labels', + 'slideshow_enabled', 'slideshow_autostart', 'slideshow_duration'], + + _attr_map: { + 'group_loop': 'loop', + 'ui_autofit': 'autofit', + 'ui_animate': 'animate', + 'ui_overlay_opacity': 'overlay_opacity', + 'ui_labels': 'labels' + }, + + /* References */ + + /** + * Item currently loaded in viewer + * @var object Content_Item + */ + item: null, + + /** + * Queued item to be loaded once viewer is available + * @var object Content_Item + */ + item_queued: null, + + /** + * Theme used by viewer + * @var object Theme + */ + theme: null, + + /* Properties */ + + item_working: null, + + active: false, + init: false, + open: false, + loading: false, + + /* Methods */ + + /* Init */ + + register_hooks: function() { + var t = this; + this + .on(['item-prev', 'item-next'], function() { + t.trigger('item-change'); + }) + .on(['close', 'item-change'], function() { + t.unload().done(function() { + t.unlock(); + }); + }); + }, + + /* References */ + + /** + * Set item reference + * Validates item before setting + * @param obj item Content_Item instance + * @return bool TRUE if valid item set, FALSE otherwise + */ + set_item: function(item) { + //Clear existing item + this.clear_item(false); + var i = this.set_component('item', item, function(item) { + return ( item.has_type() ); + }); + return ( !this.util.is_empty(i) ); + }, + + clear_item: function(full) { + //Validate + if ( !this.util.is_bool(full) ) { + full = true; + } + var item = this.get_item(); + if ( !!item ) { + item.reset(); + } + if ( full ) { + this.set_item(false); + } + }, + + /** + * Retrieve item instance current attached to viewer + * @return Content_Item|NULL Current item instance + */ + get_item: function() { + return this.get_component('item', true, false); + }, + + /** + * Retrieve theme reference + * @return object Theme reference + */ + get_theme: function() { + //Get saved theme + var ret = this.get_component('theme', false, false, false); + if ( this.util.is_empty(ret) ) { + //Theme needs to be initialized + ret = this.set_component('theme', new View.Theme(this)); + } + return ret; + }, + + /** + * Set viewer's theme + * @param object theme Theme object + */ + set_theme: function(theme) { + this.set_component('theme', theme); + }, + + /* Properties */ + + /** + * Lock the viewer + * Indicates that item is currently being processed + * @return jQuery.Deferred Resolved when item processing is complete + */ + lock: function() { + return this.set_status('item_working', $.Deferred()); + }, + + /** + * Retrieve lock + * @param bool simple (optional) Whether to return a simple status of the locked status (Default: FALSE) + * @param bool full (optional) Whether to return Deferred (TRUE) or Promise (FALSE) object (Default: FALSE) + * @return jQuery.Promise Resolved when item processing is complete + */ + get_lock: function(simple, full) { + //Validate + if ( !this.util.is_bool(simple) ) { + simple = false; + } + if ( !this.util.is_bool(full) ) { + full = false; + } + var s = 'item_working'; + //Simple status + if ( simple ) { + return this.get_status(s); + } + //Full value + var r = this.get_status(s, true); + if ( !this.util.is_promise(r) ) { + //Create default + r = this.lock(); + } + return ( full ) ? r : r.promise(); + }, + + is_locked: function() { + return this.get_lock(true); + }, + + /** + * Unlock the viewer + * Any callbacks registered for this action will be executed + * @return jQuery.Deferred Resolved instance + */ + unlock: function() { + return this.get_lock(false, true).resolve(); + }, + + /** + * Set Viewer active status + * @param bool mode (optional) Activate or deactivate status (Default: TRUE) + * @return bool Active status + */ + set_active: function(mode) { + if ( !this.util.is_bool(mode) ) { + mode = true; + } + return this.set_status('active', mode); + }, + + /** + * Check Viewer active status + * @return bool Active status + */ + is_active: function() { + return this.get_status('active'); + }, + + /** + * Set loading mode + * @param bool mode (optional) Set (TRUE) or unset (FALSE) loading mode (Default: TRUE) + * @return jQuery.Promise Promise that resolves when loading mode is set + */ + set_loading: function(mode) { + var dfr = $.Deferred(); + if ( !this.util.is_bool(mode) ) { + mode = true; + } + this.loading = mode; + //Pause/Resume slideshow + if ( this.slideshow_active() ) { + this.slideshow_pause(mode); + } + //Set CSS class on DOM element + var m = ( mode ) ? 'addClass' : 'removeClass'; + $(this.dom_get())[m]('loading'); + if ( mode ) { + //Loading transition + this.get_theme().transition('load').always(function() { + dfr.resolve(); + }); + } else { + dfr.resolve(); + } + return dfr.promise(); + }, + + /** + * Unset loading mode + * @see set_loading() + * @return jQuery.Promise Promise that resovles when loading mode is set + */ + unset_loading: function() { + return this.set_loading(false); + }, + + /** + * Retrieve loading status + * @return bool Loading status (Default: FALSE) + */ + get_loading: function() { + return ( this.util.is_bool(this.loading) ) ? this.loading : false; + }, + + /** + * Check if viewer is currently loading content + * @return bool Loading status (Default: FALSE) + */ + is_loading: function() { + return this.get_loading(); + }, + + /* Display */ + + /** + * Display content in viewer + * @param Content_Item item Item to show + * @param obj options (optional) Display options + */ + show: function(item) { + this.item_queued = item; + var fin_set = 'show_deferred'; + //Validate theme + var vt = 'theme_valid'; + var valid = true; + if ( !this.has_attribute(vt)) { + valid = this.set_attribute(vt, ( this.get_theme() && this.get_theme().get_template().get_layout(false) ) ); + } else { + valid = this.get_attribute(vt, true); + } + + if ( !valid ) { + this.close(); + return false; + } + var v = this; + var fin = function() { + //Lock viewer + v.lock(); + //Reset callback flag (for new lock) + v.set_status(fin_set, false); + //Validate request + if ( !v.set_item(v.item_queued) ) { + v.close(); + return false; + } + //Add item to history stack + v.history_add(); + //Activate + v.set_active(); + //Display + v.render(); + }; + if ( !this.is_locked() ) { + fin(); + } else if ( !this.get_status(fin_set) ) { + //Set flag to avoid duplicate callbacks + this.set_status(fin_set); + this.get_lock().always(function() { + fin(); + }); + } + }, + + /* History Management */ + + history_handle: function(e) { + var state = e.originalEvent.state; + //Load item + if ( this.util.is_int(state.item, false) ) { + this.get_parent().get_item(state.item).show({'event': e}); + this.trigger('item-change'); + } else { + var count = this.history_get(true); + //Reset count + this.history_set(0); + //Close viewer + if ( -1 !== count ) { + this.close(); + } + } + }, + + history_get: function(full) { + return this.get_status('history_count', full); + }, + history_set: function(val) { + return this.set_status('history_count', val); + }, + history_add: function() { + if ( !history.pushState ) { + return false; + } + //Get display options + var item = this.get_item(); + var opts = item.get_attribute('options_show'); + //Save history state + var count = ( this.history_get() ) ? this.history_get(true) : 0; + if ( !this.util.in_obj(opts, 'event') ) { + //Create state + var state = { + 'viewer': this.get_id(), + 'item': null, + 'count': count + }; + //Init: Save viewer state + if ( !count ) { + history.replaceState(state, null); + } + //Always: Save item state + state.item = this.get_parent().save_item(item); + state.count = ++count; + history.pushState(state, ''); + } else { + var e = opts.event.originalEvent; + if ( this.util.in_obj(e, 'state') && this.util.in_obj(e.state, 'count') ) { + count = e.state.count; + } + } + //Save history item count + this.history_set(count); + }, + history_reset: function() { + var count = this.history_get(true); + if ( count ) { + //Clear history status + this.history_set(-1); + //Restore history stack + history.go( -1 * count ); + } + }, + + /** + * Check if viewer is currently open + * Checks if node is actually visible in DOM + * @return bool TRUE if viewer is open, FALSE otherwise + */ + is_open: function() { + return ( this.dom_get().css('display') === 'none' ) ? false : true; + }, + + /** + * Load output into DOM + */ + render: function() { + //Get theme output + var v = this; + var thm = this.get_theme(); + v.dom_prep(); + //Register theme event handlers + if ( !this.get_status('render-events') ) { + this.set_status('render-events'); + thm + //Loading + .on('render-loading', function(ev, thm) { + var dfr = $.Deferred(); + if ( !v.is_active() ) { + dfr.reject(); + return dfr.promise(); + } + var set_pos = function() { + //Set position + v.dom_get().css('top', $(window).scrollTop()); + }; + var always = function() { + //Set loading flag + v.set_loading().always(function() { + dfr.resolve(); + }); + }; + if ( v.is_open() ) { + thm.transition('unload') + .fail(function() { + set_pos(); + thm.dom_get_tag('item', 'content').attr('style', ''); + }) + .always(always); + } else { + thm.transition('open') + .always(function() { + always(); + v.events_open(); + v.open = true; + }) + .fail(function() { + set_pos(); + //Fallback open + v.get_overlay().show(); + v.dom_get().show(); + }); + } + return dfr.promise(); + }) + //Complete + .on('render-complete', function(ev, thm) { + //Stop if viewer not active + if ( !v.is_active() ) { + return false; + } + //Set classes + var d = v.dom_get(); + var classes = ['item_single', 'item_multi']; + var ms = ['addClass', 'removeClass']; + if ( !v.get_item().get_group().is_single() ) { + ms.reverse(); + } + $.each(ms, function(idx, val) { + d[val](classes[idx]); + }); + //Bind events + v.events_complete(); + //Transition + thm.transition('complete') + .fail(function() { + //Autofit content + if ( v.get_attribute('autofit', true) ) { + var dims = $.extend({'display': 'inline-block'}, thm.get_item_dimensions()); + thm.dom_get_tag('item', 'content').css(dims); + } + }) + .always(function() { + //Unset loading flag + v.unset_loading(); + //Trigger event + v.trigger('render-complete'); + //Set viewer as initialized + v.init = true; + }); + }); + } + //Render + thm.render(); + }, + + /** + * Retrieve container element + * Creates default container element if not yet created + * @return jQuery Container element + */ + dom_get_container: function() { + var sel = this.get_attribute('container'); + //Set default container + if ( this.util.is_empty(sel) ) { + sel = '#' + this.add_ns('wrap'); + } + //Add default container to DOM if not yet present + var c = $(sel); + if ( !c.length ) { + //Prepare ID + var id = ( sel.indexOf('#') === 0 ) ? sel.substr(1) : sel; + //Add element + c = $('
', {'id': id}).appendTo('body'); + } + return c; + }, + + /** + * Custom Viewer DOM initialization + */ + dom_init: function() { + //Create element & add to DOM + //Save element to instance + var d = this.dom_set($('
', { + 'id': this.get_id(true), + 'class': this.get_ns() + })).appendTo(this.dom_get_container()).hide(); + //Add theme classes + var thm = this.get_theme(); + d.addClass(thm.get_classes(' ')); + //Add theme layout (basic) + var v = this; + if ( !this.get_status('render-init') ) { + this.set_status('render-init'); + thm.on('render-init', function(ev) { + //Add rendered theme layout to viewer DOM + v.dom_put('layout', ev.data); + }); + } + thm.render(true); + }, + + /** + * Prepare DOM for viewer + */ + dom_prep: function(mode) { + var m = ( this.util.is_bool(mode) && !mode ) ? 'removeClass' : 'addClass'; + $('html')[m](this.util.add_prefix('overlay')); + }, + + /** + * Restore DOM + * Required after viewer is closed + */ + dom_restore: function() { + this.dom_prep(false); + }, + + /* Layout */ + + get_layout: function() { + var ret = this.dom_get('layout', true, { + 'put_success': function() { + $(this).hide(); + } + }); + return ret; + }, + + /* Animation */ + + animation_enabled: function() { + return this.get_attribute('animate', true); + }, + + /* Overlay */ + + /** + * Determine if overlay is enabled for viewer + * @return bool TRUE if overlay is enabled, FALSE otherwise + */ + overlay_enabled: function() { + var ov = this.get_attribute('overlay_enabled'); + return ( this.util.is_bool(ov) ) ? ov : false; + }, + + /** + * Retrieve overlay DOM element + * @return jQuery Overlay element (NULL if no overlay set for viewer) + */ + get_overlay: function() { + var o = null; + var v = this; + if ( this.overlay_enabled() ) { + o = this.dom_get('overlay', true, { + 'put_success': function() { + $(this).hide().css('opacity', v.get_attribute('overlay_opacity')); + } + }); + } + return $(o); + }, + + /** + * Unload viewer + */ + unload: function() { + var dfr = $.Deferred(); + //Unload item data + this.get_theme().dom_get_tag('item').text(''); + dfr.resolve(); + return dfr.promise(); + }, + + /** + * Reset viewer + */ + reset: function() { + //Hide viewer + this.dom_get().hide(); + //Restore DOM + this.dom_restore(); + //History + this.history_reset(); + //Item + this.clear_item(); + //Reset properties + this.set_active(false); + this.set_loading(false); + this.slideshow_stop(); + this.keys_disable(); + //Clear for next item + this.get_status('item_working', true).resolve(); + }, + + /* Content */ + + get_labels: function() { + return this.get_attribute('labels', {}); + }, + + get_label: function(name) { + var lbls = this.get_labels(); + return ( name in lbls ) ? lbls[name] : ''; + }, + + /* Interactivity */ + + /** + * Initialize event handlers upon opening lightbox + */ + events_open: function() { + //Keyboard bindings + this.keys_enable(); + if ( this.open ) { + return false; + } + + //Control event bubbling + var l = this.get_layout(); + l.children().click(function(ev) { + ev.stopPropagation(); + }); + + /* Close */ + var v = this; + var close = function() { + v.close(); + }; + //Layout + l.click(close); + //Overlay + this.get_overlay().click(close); + //Fire event + this.trigger('events-open'); + }, + + /** + * Initialize event handlers upon completing lightbox rendering + */ + events_complete: function() { + if ( this.init ) { + return false; + } + //Fire event + this.trigger('events-complete'); + }, + + keys_enable: function(mode) { + if ( !this.util.is_bool(mode) ) { + mode = true; + } + var e = ['keyup', this.util.get_prefix()].join('.'); + var v = this; + var h = function(ev) { + return v.keys_control(ev); + }; + if ( mode ) { + $(document).on(e, h); + } else { + $(document).off(e); + } + }, + + keys_disable: function() { + this.keys_enable(false); + }, + + keys_control: function(ev) { + var handlers = { + 27: this.close, + 37: this.item_prev, + 39: this.item_next + }; + if ( ev.which in handlers ) { + handlers[ev.which].call(this); + return false; + } + }, + + /** + * Check if slideshow functionality is enabled + * @return bool TRUE if slideshow is enabled, FALSE otherwise + */ + slideshow_enabled: function() { + var o = this.get_attribute('slideshow_enabled'); + return ( this.util.is_bool(o) && o && this.get_item() && !this.get_item().get_group().is_single() ) ? true : false; + }, + + /** + * Checks if slideshow is currently active + * @return bool TRUE if slideshow is active, FALSE otherwise + */ + slideshow_active: function() { + return ( this.slideshow_enabled() && ( this.get_attribute('slideshow_active') || ( !this.init && this.get_attribute('slideshow_autostart') ) ) ) ? true : false; + }, + + /** + * Clear slideshow timer + */ + slideshow_clear_timer: function() { + clearInterval(this.get_attribute('slideshow_timer')); + }, + + /** + * Start slideshow timer + * @param function callback Callback function + */ + slideshow_set_timer: function(callback) { + this.set_attribute('slideshow_timer', setInterval(callback, this.get_attribute('slideshow_duration') * 1000)); + }, + + /** + * Start Slideshow + */ + slideshow_start: function() { + if ( !this.slideshow_enabled() ) { + return false; + } + this.set_attribute('slideshow_active', true); + this.dom_get().addClass('slideshow_active'); + //Clear residual timers + this.slideshow_clear_timer(); + //Start timer + var v = this; + this.slideshow_set_timer(function() { + //Pause slideshow until next item fully loaded + v.slideshow_pause(); + + //Show next item + v.item_next(); + }); + this.trigger('slideshow-start'); + }, + + /** + * Stop Slideshow + * @param bool full (optional) Full stop (TRUE) or pause (FALSE) (Default: TRUE) + */ + slideshow_stop: function(full) { + if ( !this.util.is_bool(full) ) { + full = true; + } + if ( full ) { + this.set_attribute('slideshow_active', false); + this.dom_get().removeClass('slideshow_active'); + } + //Kill timers + this.slideshow_clear_timer(); + this.trigger('slideshow-stop'); + }, + + slideshow_toggle: function() { + if ( !this.slideshow_enabled() ) { + return false; + } + if ( this.slideshow_active() ) { + this.slideshow_stop(); + } else { + this.slideshow_start(); + } + this.trigger('slideshow-toggle'); + }, + + /** + * Pause Slideshow + * @param bool mode (optional) Pause (TRUE) or Resume (FALSE) slideshow (default: TRUE) + */ + slideshow_pause: function(mode) { + //Validate + if ( !this.util.is_bool(mode) ) { + mode = true; + } + //Set viewer slideshow properties + if ( this.slideshow_active() ) { + if ( !mode ) { + //Slideshow resumed + this.slideshow_start(); + } else { + //Slideshow paused + this.slideshow_stop(false); + } + } + this.trigger('slideshow-pause'); + }, + + /** + * Resume slideshow + */ + slideshow_resume: function() { + this.slideshow_pause(false); + }, + + /** + * Next item + */ + item_next: function() { + var g = this.get_item().get_group(true); + var v = this; + var ev = 'item-next'; + var st = ['events', 'viewer', ev].join('_'); + //Setup event handler + if ( !g.get_status(st) ) { + g.set_status(st); + g.on(ev, function(e) { + v.trigger(e.type); + }); + } + g.show_next(); + }, + + /** + * Previous item + */ + item_prev: function() { + var g = this.get_item().get_group(true); + var v = this; + var ev = 'item-prev'; + var st = ['events', 'viewer', ev].join('_'); + if ( !g.get_status(st) ) { + g.set_status(st); + g.on(ev, function() { + v.trigger(ev); + }); + } + g.show_prev(); + }, + + /** + * Close viewer + */ + close: function() { + //Deactivate + this.set_active(false); + var v = this; + var thm = this.get_theme(); + thm.transition('unload') + .always(function() { + thm.transition('close', true).always(function() { + //End processes + v.reset(); + v.trigger('close'); + }); + }) + .fail(function() { + thm.dom_get_tag('item', 'content').attr('style', ''); + }); + return false; + } +}; + +View.Viewer = Component.extend(Viewer); + +/** + * Content group + * @param obj options Init options + */ +var Group = { + /* Configuration */ + + _slug: 'group', + _reciprocal: true, + _refs: { + 'current': 'Content_Item' + }, + + /* References */ + + current: null, + + /* Properties */ + + /** + * Selector for getting group items + * @var string + */ + selector: null, + + /* Methods */ + + /* Init */ + + register_hooks: function() { + var t = this; + this.on(['item-prev', 'item-next'], function() { + t.trigger('item-change'); + }); + }, + + /* Properties */ + + /** + * Retrieve selector for group items + * @return string Group items selector + */ + get_selector: function() { + if ( this.util.is_empty(this.selector) ) { + //Build selector + this.selector = this.util.format('a[%s="%s"]', this.dom_get_attribute(), this.get_id()); + } + return this.selector; + }, + + /** + * Retrieve group items + */ + get_items: function() { + var items = ( !this.util.is_empty(this.get_id()) ) ? $(this.get_selector()) : this.get_current().dom_get(); + return items; + }, + + /** + * Retrieve item at specified index + * If no index specified, first item is returned + * @param int idx Index of item to return + * @return Content_Item Item + */ + get_item: function(idx) { + //Validation + if ( !this.util.is_int(idx) ) { + idx = 0; + } + //Retrieve all items + var items = this.get_items(); + //Validate index + var max = this.get_size() - 1; + if ( idx > max ) { + idx = max; + } + //Return specified item + return items.get(idx); + }, + + /** + * Retrieve (zero-based) position of specified item in group + * @param Content_Item item Item to locate in group + * @return int Index position of item in group (-1 if item not in group) + */ + get_pos: function(item) { + if ( this.util.is_empty(item) ) { + //Get current item + item = this.get_current(); + } + return ( this.util.is_type(item, View.Content_Item) ) ? this.get_items().index(item.dom_get()) : -1; + }, + + /** + * Retrieve current item + * @return Content_Item Current item + */ + get_current: function() { + //Sanitize + if ( !this.util.is_empty(this.current) && !this.util.is_type(this.current, View.Content_Item) ) { + this.current = null; + } + return this.current; + }, + + /** + * Sets current group item + * @param Content_Item item Item to set as current + */ + set_current: function(item) { + //Validate + if ( this.util.is_type(item, View.Content_Item) ) { + //Set current item + this.current = item; + } + }, + + get_next: function(item) { + //Validate + if ( !this.util.is_type(item, View.Content_Item) ) { + item = this.get_current(); + } + if ( this.get_size() === 1 ) { + return item; + } + var next = null; + var pos = this.get_pos(item); + if ( pos !== -1 ) { + pos = ( pos + 1 < this.get_size() ) ? pos + 1 : 0; + if ( 0 !== pos || item.get_viewer().get_attribute('loop') ) { + next = this.get_item(pos); + } + } + return next; + }, + + get_prev: function(item) { + //Validate + if ( !this.util.is_type(item, View.Content_Item) ) { + item = this.get_current(); + } + if ( this.get_size() === 1 ) { + return item; + } + var prev = null; + var pos = this.get_pos(item); + if ( pos !== -1 && ( 0 !== pos || item.get_viewer().get_attribute('loop') ) ) { + if ( pos === 0 ) { + pos = this.get_size(); + } + pos -= 1; + prev = this.get_item(pos); + } + return prev; + }, + + show_next: function(item) { + if ( this.get_size() > 1 ) { + //Retrieve item + var next = this.get_next(item); + if ( !next ) { + if ( !this.util.is_type(item, View.Content_Item) ) { + item = this.get_current(); + } + item.get_viewer().close(); + } + var i = this.get_parent().get_item(next); + //Update current item + this.set_current(i); + //Show item + i.show(); + //Fire event + this.trigger('item-next'); + } + }, + + show_prev: function(item) { + if ( this.get_size() > 1 ) { + //Retrieve item + var prev = this.get_prev(item); + if ( !prev ) { + if ( !this.util.is_type(item, View.Content_Item) ) { + item = this.get_current(); + } + item.get_viewer().close(); + } + var i = this.get_parent().get_item(prev); + //Update current item + this.set_current(i); + //Show item + i.show(); + //Fire event + this.trigger('item-prev'); + } + }, + + /** + * Retrieve total number of items in group + * @return int Number of items in group + */ + get_size: function() { + return this.get_items().length; + }, + + is_single: function() { + return ( this.get_size() === 1 ); + } +}; + +View.Group = Component.extend(Group); + +/** + * Content Handler + * @param obj options Init options + */ +var Content_Handler = { + + /* Configuration */ + + _slug: 'content_handler', + _refs: { + 'item': 'Content_Item' + }, + + /* References */ + + item: null, + + /* Properties */ + + /** + * Raw layout template + * @var string + */ + template: '', + + /* Methods */ + + /* Item */ + + /** + * Check if item instance set for type + * @uses get_item() + * @uses clear_item() to remove invalid item values + * @return bool TRUE if valid item set, FALSE otherwise + */ + has_item: function() { + return ( this.util.is_empty(this.get_item()) ) ? false : true; + }, + + /** + * Retrieve item instance set on type + * @uses get_component() + * @return mixed Content_Item if valid item set, NULL otherwise + */ + get_item: function() { + return this.get_component('item', true, false); + }, + + /** + * Set item instance for type + * Items are only meant to be set/used while item is being processed + * @uses set_component() + * @param Content_Item item Item instance + * @return obj|null Item instance if item successfully set, NULL otherwise + */ + set_item: function(item) { + //Set reference + var r = this.set_component('item', item); + return r; + }, + + /** + * Clear item instance from type + * Sets value to NULL + */ + clear_item: function() { + this.item = null; + }, + + /* Evaluation */ + + /** + * Check if item matches content handler + * @param object item Content_Item instance to check for type match + * @return bool TRUE if type matches, FALSE otherwise + */ + match: function(item) { + //Validate + var attr = 'match'; + var m = this.get_attribute(attr); + //Stop processing types with no matching algorithm + if ( !this.util.is_empty(m) ) { + //Process regex patterns + + //String-based + if ( this.util.is_string(m) ) { + //Create new regexp object + m = new RegExp(m, "i"); + this.set_attribute(attr, m); + } + //RegExp based + if ( this.util.is_type(m, RegExp) ) { + return m.test(item.get_uri()); + } + //Process function + if ( this.util.is_func(m) ) { + return ( m.call(this, item) ) ? true : false; + } + } + //Default + return false; + }, + + /* Processing/Output */ + + /** + * Loads item data + * @param obj item Content item to load data for + * @return obj Promise that is resolved when item data is loaded + */ + load: function(item) { + var dfr = $.Deferred(); + var ret = this.call_attribute('load', item, dfr); + //Handle missing load method + if ( null === ret ) { + dfr.resolve(); + } + return dfr.promise(); + }, + + /** + * Render output to display item + * @param Content_Item item Item to render output for + * @return obj jQuery.Promise that is resolved when item is rendered + */ + render: function(item) { + var dfr = $.Deferred(); + //Validate + this.call_attribute('render', item, dfr); + return dfr.promise(); + } +}; + +View.Content_Handler = Component.extend(Content_Handler); + +/** + * Content Item + * @param obj options Init options + */ +var Content_Item = { + /* Configuration */ + + _slug: 'content_item', + _reciprocal: true, + _refs: { + 'viewer': 'Viewer', + 'group': 'Group', + 'type': 'Content_Handler' + }, + _containers: ['group'], + + _attr_default: { + source: null, + permalink: null, + dimensions: null, + title: '', + group: null, + internal: false, + output: null + }, + + /* References */ + + group: null, + viewer: null, + type: null, + + /* Properties */ + + data: null, + loaded: null, + + /* Init */ + + _c: function(el) { + //Save element to instance + this.dom_set(el); + //Default initialization + this._super(); + }, + + /* Methods */ + + /*-** Attributes **-*/ + + /** + * Build default attributes + * Populates attributes with asset properties (attachments) + * Overrides super class method + * @uses Component.init_default_attributes() + */ + init_default_attributes: function() { + this._super(); + //Add asset properties + var d = this.dom_get(); + var key = d.attr('href') || null; + var assets = this.get_parent().assets || null; + //Merge asset data with default attributes + if ( this.util.is_string(key) ) { + var attrs = [{}, this._attr_default, {'permalink': key}]; + if ( this.util.is_obj(assets) ) { + var t = this; + /** + * Retrieve item assets + * Handles variant items as well (Retrieves parent item assets) + * @param string key Item URI + * @return obj Item assets (Empty if no match) + */ + var get_assets = function(key) { + var ret = {}; + if ( key in assets && t.util.is_obj(assets[key]) ) { + ret = assets[key]; + //Handle variants + if ( '_parent' in ret ) { + ret = get_assets(ret._parent); + } + } + return ret; + }; + //Save assets + attrs.push(get_assets(key)); + } + this._attr_default = $.extend.apply(this, attrs); + } + return this._attr_default; + }, + + /*-** Properties **-*/ + + /** + * Retrieve item output + * Output generated based on content handler if not previously generated + * @uses get_attribute() to retrieve cached output + * @uses set_attribute() to cache generated output + * @uses get_type() to retrieve item type + * @uses Content_Handler.render() to generate item output + * @return obj jQuery.Promise that is resolved when output is retrieved + */ + get_output: function() { + var dfr = $.Deferred(); + //Check for cached output + var ret = this.get_attribute('output'); + if ( this.util.is_string(ret) ) { + dfr.resolve(ret); + } else if ( this.has_type() ) { + //Render output from scratch (if necessary) + //Get item type + var type = this.get_type(); + //Render type-based output + var item = this; + type.render(this).done(function(output) { + //Cache output + item.set_output(output); + dfr.resolve(output); + }); + } else { + dfr.resolve(''); + } + return dfr.promise(); + }, + + /** + * Cache output for future retrieval + * @uses set_attribute() to cache output + */ + set_output: function(out) { + if ( this.util.is_string(out, false) ) { + this.set_attribute('output', out); + } + }, + + /** + * Retrieve item output + * Alias for `get_output()` + * @return jQuery.Promise Deferred that is resolved when content is retrieved + */ + get_content: function() { + return this.get_output(); + }, + + /** + * Retrieve item URI + * @param string mode (optional) Which URI should be retrieved + * > source: Media source + * > permalink: Item permalink + * @return string Item URI + */ + get_uri: function(mode) { + //Validate + if ( $.inArray(mode ,['source', 'permalink']) === -1 ) { + mode = 'source'; + } + //Retrieve URI + var ret = this.get_attribute(mode); + if ( !this.util.is_string(ret) ) { + ret = ( 'source' === mode ) ? this.get_attribute('permalink') : ''; + } + return ret; + }, + + /** + * Retrieve item title + */ + get_title: function() { + var prop = 'title'; + var prop_cached = prop + '_cached'; + //Check for cached value + if ( this.has_attribute(prop_cached) ) { + return this.get_attribute(prop_cached, ''); + } + + var title = ''; + var sel_cap = '.wp-caption-text'; + //Generate title from DOM values + var dom = this.dom_get(); + + //Standalone link + if ( dom.length && !this.in_gallery() ) { + //Link title + title = dom.attr(prop); + + //Caption + if ( !title ) { + title = dom.siblings(sel_cap).html(); + } + } + + //Saved attributes + if ( !title ) { + var props = ['caption', 'title']; + for ( var x = 0; x < props.length; x++ ) { + title = this.get_attribute(props[x], ''); + if ( !this.util.is_empty(title) ) { + break; + } + } + } + + //Fallbacks + if ( !title && dom.length ) { + //Alt attribute + title = dom.find('img').first().attr('alt'); + + //Element text + if ( !title ) { + title = dom.text(); + } + } + + //Validate + if ( !this.util.is_string(title, false) ) { + title = ''; + } + + //Cache retrieved value + this.set_attribute(prop_cached, title); + //Return value + return title; + }, + + /** + * Retrieve item dimensions + * @return obj Item `width` and `height` properties (px) + */ + get_dimensions: function() { + return $.extend({'width': 0, 'height': 0}, this.get_attribute('dimensions'), {}); + }, + + /** + * Save item data to instance + * Item data is saved when rendered + * @param mixed data Item data (property cleared if NULL) + */ + set_data: function(data) { + this.data = data; + }, + + get_data: function() { + return this.data; + }, + + /** + * Determine gallery type + * @return string|null Gallery type ID (NULL if item not in gallery) + */ + gallery_type: function() { + var ret = null; + var types = { + 'wp': '.gallery-icon', + 'ngg': '.ngg-gallery-thumbnail' + }; + + var dom = this.dom_get(); + for ( var type in types ) { + if ( dom.parent(types[type]).length > 0 ) { + ret = type; + break; + } + } + return ret; + }, + + /** + * Check if current link is part of a gallery + * @param string gType (optional) Gallery type to check for + * @return bool TRUE if link is part of (specified) gallery (FALSE otherwise) + */ + in_gallery: function(gType) { + var type = this.gallery_type(); + //No gallery + if ( null == type ) { + return false; + } + //Boolean check + if ( !this.util.is_string(gType) ) { + return true; + } + //Check for specific gallery type + return ( gType === type ) ? true : false; + }, + + /*-** Component References **-*/ + + /* Viewer */ + + get_viewer: function() { + return this.get_component('viewer'); + }, + + /** + * Sets item's viewer property + * @uses View.get_viewer() to retrieve global viewer + * @uses this.viewer to save item's viewer + * @param string|View.Viewer v Viewer to set for item + * > Item's viewer is reset if invalid viewer provided + */ + set_viewer: function(v) { + return this.set_component('viewer', v); + }, + + /* Group */ + + /** + * Retrieve item's group + * @param bool set_current (optional) Sets item as current item in group (Default: FALSE) + * @return View.Group|bool Group reference item belongs to (FALSE if no group) + */ + get_group: function(set_current) { + var prop = 'group'; + //Check if group reference already set + var g = this.get_component(prop, true, false, false); + if ( g ) { + } else { + //Set empty group if no group exists + g = this.set_component(prop, new View.Group()); + set_current = true; + } + if ( !!set_current ) { + g.set_current(this); + } + return g; + }, + + /** + * Sets item's group property + * @uses View.get_group() to retrieve global group + * @uses this.group to set item's group + * @param string|View.Group g Group to set for item + * > Item's group is reset if invalid group provided + */ + set_group: function(g) { + //If group ID set, get object reference + if ( this.util.is_string(g) ) { + g = this.get_parent().get_group(g); + } + + //Set (or clear) group property + this.group = ( this.util.is_type(g, View.Group) ) ? g : false; + }, + + /* Content Handler */ + + /** + * Retrieve item type + * @uses get_component() to retrieve saved reference to Content_Handler instance + * @uses View.get_content_handler() to determine item content handler (if necessary) + * @return Content_Handler|null Content Handler of item (NULL no valid type exists) + */ + get_type: function() { + var t = this.get_component('type', false, false, false); + if ( !t ) { + t = this.set_type(this.get_parent().get_content_handler(this)); + } + return t; + }, + + /** + * Save content handler reference + * @uses set_component() to save type reference + * @return Content_Handler|null Saved content handler (NULL if invalid) + */ + set_type: function(type) { + return this.set_component('type', type); + }, + + /** + * Check if content handler exists for item + * @return bool TRUE if content handler exists, FALSE otherwise + */ + has_type: function() { + var ret = !this.util.is_empty(this.get_type()); + return ret; + }, + + /* Actions */ + + /** + * Display item in viewer + * @uses get_viewer() to retrieve viewer instance for item + * @uses Viewer.show() to display item in viewer + * @param obj options (optional) Options + */ + show: function(options) { + //Validate content handler + if ( !this.has_type() ) { + return false; + } + //Set display options + this.set_attribute('options_show', options); + //Retrieve viewer + var v = this.get_viewer(); + //Load item + this.load(); + var ret = v.show(this); + return ret; + }, + + /** + * Load item data + * + * Retrieves item data from external sources (if necessary) + * @uses this.loaded to save loaded state + * @return obj Promise that is resolved when item data is loaded + */ + load: function() { + if ( !this.util.is_promise(this.loaded) ) { + //Load item data (via content handler) + this.loaded = this.get_type().load(this); + } + return this.loaded.promise(); + }, + + reset: function() { + this.set_attribute('options_show', null); + } +}; + +View.Content_Item = Component.extend(Content_Item); + +/** + * Modeled Component + */ +var Modeled_Component = { + + _slug: 'modeled_component', + + /* Methods */ + + /* Attributes */ + + /** + * Retrieve attribute + * Gives priority to model values + * @see Component.get_attribute() + * @param string key Attribute to retrieve + * @param mixed def (optional) Default value (Default: NULL) + * @param bool check_model (optional) Check model for value (Default: TRUE) + * @param bool enforce_type (optional) Return value data type should match default value data type (Default: TRUE) + * @return mixed Attribute value + */ + get_attribute: function(key, def, check_model, enforce_type) { + //Validate + if ( !this.util.is_string(key) ) { + //Invalid requests sent straight to super method + return this._super(key, def, enforce_type); + } + if ( !this.util.is_bool(check_model) ) { + check_model = true; + } + var ret = null; + //Check model for attribute + if ( check_model ) { + var m = this.get_ancestor(key, false); + if ( this.util.in_obj(m, key) ) { + ret = m[key]; + } + } + //Check standard attributes as fallback + if ( null == ret ) { + ret = this._super(key, def, enforce_type); + } + return ret; + }, + + /** + * Set attribute value + * Gives priority to model values + * @see Component.set_attribute() + * @param string key Attribute to set + * @param mixed val Value to set for attribute + * @param bool|obj use_model (optional) Set the value on the model (Default: TRUE) + * > bool: Set attribute on current model (TRUE) or as standard attribute (FALSE) + * > obj: Model object to set attribute on + * @return mixed Attribute value + */ + set_attribute: function(key, val, use_model) { + //Validate + if ( ( !this.util.is_string(key) ) || !this.util.is_set(val) ) { + return false; + } + if ( !this.util.is_bool(use_model) && !this.util.is_obj(use_model) ) { + use_model = true; + } + //Determine where to set attribute + if ( !!use_model ) { + var model = this.util.is_obj(use_model) ? use_model : this.get_model(); + + //Set attribute in model + model[key] = val; + } else { + //Set as standard attribute + this._super(key, val); + } + return val; + }, + + + /* Model */ + + /** + * Retrieve Template model + * @return obj Model (Default: Empty object) + */ + get_model: function() { + var m = this.get_attribute('model', null, false); + if ( !this.util.is_obj(m) ) { + //Set default value + m = {}; + this.set_attribute('model', m, false); + } + return m; + }, + + + /** + * Check if instance has model + * @return bool TRUE if model is set, FALSE otherwise + */ + has_model: function() { + return ( this.util.is_empty( this.get_model() ) ) ? false : true; + }, + + + + /** + * Check if specified attribute exists in model + * @param string key Attribute to check for + * @return bool TRUE if attribute exists, FALSE otherwise + */ + in_model: function(key) { + return ( this.util.in_obj(this.get_model(), key) ) ? true : false; + }, + + /** + * Retrieve all ancestor models + * @param bool inc_current (optional) Include current model in list (Default: FALSE) + * @return array Theme ancestor models (Closest parent first) + */ + get_ancestors: function(inc_current) { + var ret = []; + var m = this.get_model(); + while ( this.util.is_obj(m) ) { + ret.push(m); + m = ( this.util.in_obj(m, 'parent') && this.util.is_obj(m.parent) ) ? m.parent : null; + } + //Remove current model from list + if ( !inc_current ) { + ret.shift(); + } + return ret; + }, + + /** + * Retrieve first ancestor of current theme with specified attribute + * > Current model is also evaluated + * @param string attr Attribute to search ancestors for + * @param bool safe_mode (optional) Return current model if no matching ancestor found (Default: TRUE) + * @return obj Theme ancestor (Default: Current theme model) + */ + get_ancestor: function(attr, safe_mode) { + //Validate + if ( !this.util.is_string(attr) ) { + return false; + } + if ( !this.util.is_bool(safe_mode) ) { + safe_mode = true; + } + var mcurr = this.get_model(); + var m = mcurr; + var found = false; + while ( this.util.is_obj(m) ) { + //Check if attribute exists in model + if ( this.util.in_obj(m, attr) && !this.util.is_empty(m[attr]) ) { + found = true; + break; + } + //Get next model + m = ( this.util.in_obj(m, 'parent') ) ? m['parent'] : null; + } + if ( !found ) { + if ( safe_mode ) { + //Use current model as fallback + if ( this.util.is_empty(m) ) { + m = mcurr; + } + //Add attribute to object + if ( !this.util.in_obj(m, attr) ) { + m[attr] = null; + } + } else { + m = null; + } + } + return m; + } + +}; + +Modeled_Component = Component.extend(Modeled_Component); + +/** + * Theme + */ +var Theme = { + + /* Configuration */ + + _slug: 'theme', + _refs: { + 'viewer': 'Viewer', + 'template': 'Template' + }, + _models: {}, + + _containers: ['viewer'], + + _attr_default: { + template: null, + model: null + }, + + /* References */ + + viewer: null, + template: null, + + /* Methods */ + + /** + * Custom constructor + * @see Component._c() + */ + _c: function(id, attributes, viewer) { + //Validate + if ( arguments.length === 1 && this.util.is_type(arguments[0], View.Viewer) ) { + viewer = arguments[0]; + id = null; + } + //Pass parameters to parent constructor + this._super(id, attributes); + + //Set viewer instance + this.set_viewer(viewer); + + //Set theme model + this.set_model(id); + }, + + /* Viewer */ + + get_viewer: function() { + return this.get_component('viewer', false, true, false); + }, + + /** + * Sets theme's viewer property + * @uses View.get_viewer() to retrieve global viewer + * @uses this.viewer to save item's viewer + * @param string|View.Viewer v Viewer to set for item + * > Theme's viewer is reset if invalid viewer provided + */ + set_viewer: function(v) { + return this.set_component('viewer', v); + }, + + /* Template */ + + /** + * Retrieve template instance + * @return Template instance + */ + get_template: function() { + //Get saved template + var ret = this.get_component('template', true, false, false); + //Template needs to be initialized + if ( this.util.is_empty(ret) ) { + //Pass model to Template instance + var attr = { 'theme': this, 'model': this.get_model() }; + ret = this.set_component('template', new View.Template(attr)); + } + return ret; + }, + + /** + * Retrieve tags from template + * All tags will be retrieved by default + * Specific tag/property instances can be retrieved as well + * @see Template.get_tags() + * @param string name (optional) Name of tags to retrieve + * @param string prop (optional) Specific tag property to retrieve + * @return array Tags in template + */ + get_tags: function(name, prop) { + return this.get_template().get_tags(name, prop); + }, + + /** + * Retrieve tag DOM elements + * @see Template.dom_get_tag() + */ + dom_get_tag: function(tag, prop) { + return $(this.get_template().dom_get_tag(tag, prop)); + }, + + /* Model */ + + /** + * Retrieve theme models + * @return obj Theme models + */ + get_models: function() { + return this._models; + }, + + /** + * Retrieve specified theme model + * @param string id (optional) Theme model to retrieve + * > Default model retrieved if ID is invalid/not set + * @return obj Specified theme model + */ + get_model: function(id) { + var ret = null; + //Pass request to superclass method + if ( !this.util.is_set(id) && this.util.is_obj( this.get_attribute('model', null, false) ) ) { + ret = this._super(); + } else { + //Retrieve matching theme model + var models = this.get_models(); + if ( !this.util.is_string(id) ) { + id = this.get_parent().get_option('theme_default'); + } + //Select first theme model if specified model is invalid + if ( !this.util.in_obj(models, id) ) { + id = $.map(models, function(v, key) { return key; })[0]; + } + ret = models[id]; + } + return ret; + }, + + /** + * Set model for current theme instance + * @param string id (optional) Theme ID (Default theme retrieved if ID invalid) + */ + set_model: function(id) { + this.set_attribute('model', this.get_model(id), false); + //Set ID using model attributes (if necessary) + if ( !this.check_id(true) ) { + var m = this.get_model(); + if ( 'id' in m ) { + this.set_id(m.id); + } + } + }, + + /* Properties */ + + /** + * Generate class names for DOM node + * @param string rtype (optional) Return data type + * > Default: array + * > If string supplied: Joined classes delimited by parameter + * @uses get_class() to generate class names + * @uses Array.join() to convert class names array to string + * @return array Class names + */ + get_classes: function(rtype) { + //Build array of class names + var cls = []; + var thm = this; + //Include theme parent's class name + var models = this.get_ancestors(true); + $.each(models, function(idx, model) { + cls.push(thm.add_ns(model.id)); + }); + //Convert class names array to string + if ( this.util.is_string(rtype) ) { + cls = cls.join(rtype); + } + //Return class names + return cls; + }, + + /** + * Get custom measurement + * @param string attr Measurement to retrieve + * @param obj def (optional) Default value + * @return obj Attribute measurements + */ + get_measurement: function(attr, def) { + var meas = null; + //Validate + if ( !this.util.is_string(attr) ) { + return meas; + } + if ( !this.util.is_obj(def, false) ) { + def = {}; + } + //Manage cache + var attr_cache = this.util.format('%s_cache', attr); + var cache = this.get_attribute(attr_cache, {}, false); + var status = '_status'; + var item = this.get_viewer().get_item(); + var w = $(window); + //Check cache freshness + if ( !( status in cache ) || !this.util.is_obj(cache[status]) || cache[status].width !== w.width() || cache[status].height !== w.height() ) { + cache = {}; + } + if ( this.util.is_empty(cache) ) { + //Set status + cache[status] = { + 'width': w.width(), + 'height': w.height(), + 'index': [] + }; + } + //Retrieve cached values + var pos = $.inArray(item, cache[status].index); + if ( pos !== -1 && pos in cache ) { + meas = cache[pos]; + } + //Generate measurement + if ( !this.util.is_obj(meas) ) { + //Get custom theme measurement + meas = this.call_attribute(attr); + if ( !this.util.is_obj(meas) ) { + //Retrieve fallback value + meas = this.get_measurement_default(attr); + } + } + //Normalize measurement + meas = ( this.util.is_obj(meas) ) ? $.extend({}, def, meas) : def; + //Cache measurement + pos = cache[status].index.push(item) - 1; + cache[pos] = meas; + this.set_attribute(attr_cache, cache, false); + //Return measurement (copy) + return $.extend({}, meas); + }, + + /** + * Get default measurement using attribute's default handler + * @param string attr Measurement attribute + * @return obj Measurement values + */ + get_measurement_default: function(attr) { + //Validate + if ( !this.util.is_string(attr) ) { + return null; + } + //Find default handler + attr = this.util.format('get_%s_default', attr); + if ( this.util.in_obj(this, attr) ) { + attr = this[attr]; + if ( this.util.is_func(attr) ) { + //Execute default handler + attr = attr.call(this); + } + } else { + attr = null; + } + return attr; + }, + + /** + * Retrieve theme offset + * @return obj Theme offset with `width` & `height` properties + */ + get_offset: function() { + return this.get_measurement('offset', { 'width': 0, 'height': 0}); + }, + + /** + * Generate default offset + * @return obj Theme offsets with `width` & `height` properties + */ + get_offset_default: function() { + var offset = { 'width': 0, 'height': 0 }; + var v = this.get_viewer(); + var vn = v.dom_get(); + //Clone viewer + var vc = vn + .clone() + .attr('id', '') + .css({'visibility': 'hidden', 'position': 'absolute', 'top': ''}) + .removeClass('loading') + .appendTo(vn.parent()); + //Get offset from layout node + var l = vc.find(v.dom_get_selector('layout')); + if ( l.length ) { + //Clear inline styles + l.find('*').css({ + 'width': '', + 'height': '', + 'display': '' + }); + //Resize content nodes + var tags = this.get_tags('item', 'content'); + if ( tags.length ) { + var offset_item = v.get_item().get_dimensions(); + //Set content dimensions + tags = $(l.find(tags[0].get_selector('full')).get(0)).css({'width': offset_item.width, 'height': offset_item.height}); + $.each(offset_item, function(key, val) { + offset[key] = -1 * val; + }); + } + + //Set offset + offset.width += l.width(); + offset.height += l.height(); + //Normalize + $.each(offset, function(key, val) { + if ( val < 0 ) { + offset[key] = 0; + } + }); + } + vc.empty().remove(); + return offset; + }, + + /** + * Retrieve theme margins + * @return obj Theme margin with `width` & `height` properties + */ + get_margin: function() { + return this.get_measurement('margin', {'width': 0, 'height': 0}); + }, + + /** + * Retrieve item dimensions + * Dimensions are adjusted to fit window (if necessary) + * @return obj Item dimensions with `width` & `height` properties + */ + get_item_dimensions: function() { + var v = this.get_viewer(); + var dims = v.get_item().get_dimensions(); + if ( v.get_attribute('autofit', false) ) { + //Get maximum dimensions + var margin = this.get_margin(); + var offset = this.get_offset(); + offset.height += margin.height; + offset.width += margin.width; + var max = {'width': $(window).width(), 'height': $(window).height() }; + if ( max.width > offset.width ) { + max.width -= offset.width; + } + if ( max.height > offset.height ) { + max.height -= offset.height; + } + //Get resize factor + var factor = Math.min(max.width / dims.width, max.height / dims.height); + //Resize dimensions + if ( factor < 1 ) { + $.each(dims, function(key) { + dims[key] = Math.round(dims[key] * factor); + }); + } + } + return $.extend({}, dims); + }, + + /** + * Retrieve theme dimensions + * @return obj Theme dimensions with `width` & `height` properties + */ + get_dimensions: function() { + var dims = this.get_item_dimensions(); + var offset = this.get_offset(); + $.each(dims, function(key) { + dims[key] += offset[key]; + }); + return dims; + }, + + /* Output */ + + /** + * Render Theme output + * @param bool init (optional) Initialize theme (Default: FALSE) + * @see Template.render() + */ + render: function(init) { + var thm = this; + var tpl = this.get_template(); + var st = 'events_render'; + if ( !this.get_status(st) ) { + this.set_status(st); + //Register events + tpl.on([ + 'render-init', + 'render-loading', + 'render-complete' + ], + function(ev) { + return thm.trigger(ev.type, ev.data); + }); + } + //Render template + tpl.render(init); + }, + + transition: function(event, clear_queue) { + var dfr = null; + var attr = 'transition'; + var v = this.get_viewer(); + var fx_temp = null; + var anim_on = v.animation_enabled(); + if ( v.get_attribute(attr, true) && this.util.is_string(event) ) { + var anim_stop = function() { + var l = v.get_layout(); + l.find('*').each(function() { + var el = $(this); + while ( el.queue().length ) { + el.stop(false, true); + } + }); + }; + //Stop queued animations + if ( !!clear_queue ) { + anim_stop(); + } + //Get transition handlers + var attr_set = [attr, 'set'].join('_'); + var trns; + if ( !this.get_attribute(attr_set) ) { + var models = this.get_ancestors(true); + trns = []; + this.set_attribute(attr_set, true); + var thm = this; + $.each(models, function(idx, model) { + if ( attr in model && thm.util.is_obj(model[attr]) ) { + trns.push(model[attr]); + } + }); + //Merge transition handlers into current theme + trns.push({}); + trns = this.set_attribute(attr, $.extend.apply($, trns.reverse())); + } else { + trns = this.get_attribute(attr, {}); + } + if ( this.util.is_method(trns, event) ) { + //Disable animations if necessary + if ( !anim_on ) { + fx_temp = $.fx.off; + $.fx.off = true; + } + //Pass control to transition event + dfr = trns[event].call(this, v, $.Deferred()); + } + } + if ( !this.util.is_promise(dfr) ) { + dfr = $.Deferred(); + dfr.reject(); + } + dfr.always(function() { + //Restore animation state + if ( null !== fx_temp ) { + $.fx.off = fx_temp; + } + }); + return dfr.promise(); + } +}; + +View.Theme = Modeled_Component.extend(Theme); + +/** + * Template handler + * Parses and Builds layout from raw template + */ +var Template = { + /* Configuration */ + + _slug: 'template', + _reciprocal: true, + + _refs: { + 'theme': 'Theme' + }, + _containers: ['theme'], + + _attr_default: { + /** + * URI to layout (raw) file + * @var string + */ + layout_uri: '', + + /** + * Raw layout template + * @var string + */ + layout_raw: '', + /** + * Parsed layout + * Placeholders processed + * @var string + */ + layout_parsed: '', + /** + * Tags in template + * Populated once template has been parsed + * @var array + */ + tags: null, + /** + * Model to use for properties + * Usually reference to an object in other component + * @var obj + */ + model: null + }, + + /* References */ + + theme: null, + + /* Methods */ + + _c: function(attributes) { + this._super('', attributes); + }, + + get_theme: function() { + var ret = this.get_component('theme', true, false, false); + return ret; + }, + + /* Output */ + + /** + * Render output + * @param bool init (optional) Whether to initialize layout (TRUE) or render item (FALSE) (Default: FALSE) + * Events + * > render-init: Initialize template + * > render-loading: DOM elements created and item content about to be loaded + * > render-complete: Item content loaded, ready for display + */ + render: function(init) { + var v = this.get_theme().get_viewer(); + if ( !this.util.is_bool(init) ) { + init = false; + } + //Populate layout + if ( !init ) { + if ( !v.is_active() ) { + return false; + } + var item = v.get_item(); + if ( !this.util.is_type(item, View.Content_Item) ) { + v.close(); + return false; + } + //Iterate through tags and populate layout + if ( v.is_active() && this.has_tags() ) { + var loading_promise = this.trigger('render-loading'); + var tpl = this; + var tags = this.get_tags(), + tag_promises = []; + //Render Tag output + $.when(item.load(), loading_promise).done(function() { + if ( !v.is_active() ) { + return false; + } + $.each(tags, function(idx, tag) { + if ( !v.is_active() ) { + return false; + } + tag_promises.push(tag.render(item).done(function(r) { + if ( !v.is_active() ) { + return false; + } + r.tag.dom_get().html(r.output); + })); + }); + //Fire event when all tags rendered + if ( !v.is_active() ) { + return false; + } + $.when.apply($, tag_promises).done(function() { + tpl.trigger('render-complete'); + }); + }); + } + } else { + //Get Layout (basic) + this.trigger('render-init', this.dom_get()); + } + }, + + /*-** Layout **-*/ + + /** + * Retrieve layout + * @param bool parsed (optional) TRUE retrieves parsed layout, FALSE retrieves raw layout (Default: TRUE) + * @return string Layout (HTML) + */ + get_layout: function(parsed) { + //Validate + if ( !this.util.is_bool(parsed) ) { + parsed = true; + } + //Determine which layout to retrieve (raw/parsed) + var l = ( parsed ) ? this.parse_layout() : this.get_attribute('layout_raw', ''); + return l; + }, + + /** + * Parse layout + * Converts template tags to HTML elements + * > Template tag properties saved to HTML elements for future initialization + * Returns saved layout if previously parsed + * @return string Parsed layout + */ + parse_layout: function() { + //Check for previously-parsed layout + var a = 'layout_parsed'; + var ret = this.get_attribute(a); + //Return cached layout immediately + if ( this.util.is_string(ret) ) { + return ret; + } + //Parse raw layout + ret = this.sanitize_layout( this.get_layout(false) ); + ret = this.parse_tags(ret); + //Save parsed layout + this.set_attribute(a, ret); + + //Return parsed layout + return ret; + }, + + /** + * Sanitize layout + * @param obj|string l Layout string or jQuery object + * @return obj|string Sanitized layout (Same data type that was passed to method) + */ + sanitize_layout: function(l) { + //Stop processing if invalid value + if ( this.util.is_empty(l) ) { + return l; + } + //Set return type + var rtype = ( this.util.is_string(l) ) ? 'string' : null; + /* Quarantine hard-coded tags */ + + //Create DOM structure from raw template + var dom = $(l); + //Find hard-coded tag nodes + var tag_temp = new View.Template_Tag(); + var cls = tag_temp.get_class(); + var cls_new = ['x', cls].join('_'); + $(tag_temp.get_selector(), dom).each(function() { + //Replace matching class name with blocking class + $(this).removeClass(cls).addClass(cls_new); + }); + //Format return value + switch ( rtype ) { + case 'string' : + dom = dom.wrap('
').parent().html(); + l = dom; + break; + default : + l = dom; + } + return l; + }, + + /*-** Tags **-*/ + + /** + * Extract tags from template + * Tags are replaced with DOM element placeholders + * Extracted tags are saved as element attribute values (for future use) + * @param string l Raw layout to parse + * @return string Parsed layout + */ + parse_tags: function(l) { + //Validate + if ( !this.util.is_string(l) ) { + return ''; + } + //Parse tags in layout + //Tag regex + var re = /\{{2}\s*(\w.*?)\s*\}{2}/gim; + //Tag match results + var match; + //Iterate through template and find tags + while ( match = re.exec(l) ) { + //Replace tag in layout with DOM container + l = l.substring(0, match.index) + this.get_tag_container(match[1]) + l.substring(match.index + match[0].length); + } + return l; + }, + + /** + * Create DOM element container for tag + * @param string Tag ID (will be prefixed) + * @return string DOM element + */ + get_tag_container: function(tag) { + //Build element + var attr = this.get_tag_attribute(); + return this.util.format('', attr, encodeURI(tag)); + }, + + get_tag_attribute: function() { + return this.get_parent().get_component_temp(View.Template_Tag).dom_get_attribute(); + }, + + /** + * Retrieve Template_Tag instance at specified index + * @param int idx (optional) Index to retrieve tag from + * @return Template_Tag Tag instance + */ + get_tag: function(idx) { + var ret = null; + if ( this.has_tags() ) { + var tags = this.get_tags(); + if ( !this.util.is_int(idx) || 0 > idx || idx >= tags.length ) { + idx = 0; + } + ret = tags[idx]; + } + return ret; + }, + + /** + * Retrieve tags from template + * Subset of tags may be retrieved based on parameter values + * Template is parsed if tags not set + * @param string name (optional) Tag type to retrieve instances of + * @param string prop (optional) Tag property to retrieve instances of + * @return array Template_Tag instances + */ + get_tags: function(name, prop) { + var a = 'tags'; + var tags = this.get_attribute(a); + //Initialize tags + if ( !this.util.is_array(tags) ) { + tags = []; + //Retrieve layout DOM tree + var d = this.dom_get(); + //Select tag nodes + var attr = this.get_tag_attribute(); + var nodes = $(d).find('[' + attr + ']'); + //Build tag instances from nodes + $(nodes).each(function() { + //Get tag placeholder + var el = $(this); + var tag = new View.Template_Tag(decodeURI(el.attr(attr))); + //Populate valid tags + if ( tag.has_handler() ) { + //Add tag to array + tags.push(tag); + //Connect tag to DOM node + tag.dom_set(el); + //Set classes + el.addClass(tag.get_classes(' ')); + } + //Clear data attribute + el.removeAttr(attr); + }); + //Save tags + this.set_attribute(a, tags, false); + } + tags = this.get_attribute(a, [], false); + //Filter tags by parameters + if ( !this.util.is_empty(tags) && this.util.is_string(name) ) { + //Normalize + if ( !this.util.is_string(prop) ) { + prop = false; + } + var tags_filtered = []; + var tc = null; + for ( var x = 0; x < tags.length; x++ ) { + tc = tags[x]; + if ( name === tc.get_name() ) { + //Check tag property + if ( !prop || prop === tc.get_prop() ) { + tags_filtered.push(tc); + } + } + } + tags = tags_filtered; + } + return ( this.util.is_array(tags, false) ) ? tags : []; + }, + + /** + * Check if template contains tags + * @return bool TRUE if tags exist, FALSE otherwise + */ + has_tags: function() { + return ( this.get_tags().length > 0 ) ? true : false; + }, + + /*-** DOM **-*/ + + /** + * Custom DOM initialization + */ + dom_init: function() { + //Create DOM object from parsed layout + this.dom_set(this.get_layout()); + }, + + /** + * Retrieve DOM element(s) for specified tag + * @param string tag Name of tag to retrieve + * @param string prop (optional) Specific tag property to retrieve + * @return array DOM elements for tag + */ + dom_get_tag: function(tag, prop) { + var ret = $(); + var tags = this.get_tags(tag, prop); + if ( tags.length ) { + //Build selector + var level = null; + if ( this.util.is_string(tag) ) { + level = ( this.util.is_string(prop) ) ? 'full' : 'tag'; + } + var sel = '.' + tags[0].get_class(level); + ret = this.dom_get().find(sel); + } + return ret; + } +}; + +View.Template = Modeled_Component.extend(Template); + +/** + * Template tag + */ +var Template_Tag = { + /* Configuration */ + _slug: 'template_tag', + _reciprocal: true, + /* Properties */ + _attr_default: { + name: null, + prop: null, + match: null + }, + /** + * Tag Handlers + * Collection of Template_Tag_Handler instances + * @var obj + */ + handlers: {}, + /* Methods */ + + /** + * Constructor + * @param + */ + _c: function(tag_match) { + this.parse(tag_match); + }, + + /** + * Set instance attributes using tag extracted from template + * @param string tag_match Extracted tag match + */ + parse: function(tag_match) { + //Return default value for invalid instances + if ( !this.util.is_string(tag_match) ) { + return false; + } + //Parse instance options + var parts = tag_match.split('|'), + part; + if ( !parts.length ) { + return null; + } + var attrs = { + name: null, + prop: null, + match: tag_match + }; + //Get tag ID + attrs.name = parts[0]; + //Get main property + if ( attrs.name.indexOf('.') !== -1 ) { + attrs.name = attrs.name.split('.', 2); + attrs.prop = attrs.name[1]; + attrs.name = attrs.name[0]; + } + //Get other attributes + for ( var x = 1; x < parts.length; x++ ) { + part = parts[x].split(':', 1); + if ( part.length > 1 && !( part[0] in attrs ) ) { + //Add key/value pair to attributes + attrs[part[0]] = part[1]; + } + } + //Save to instance + this.set_attributes(attrs, true); + }, + + /** + * Render tag output + * @param Content_Item item + * @return obj jQuery.Promise object that is resolved when tag is rendered + * Parameters passed to callbacks + * > tag obj Current tag instance + * > output string Tag output + */ + render: function(item) { + var tag = this; + return tag.get_handler().render(item, tag).pipe(function(output) { + return {'tag': tag, 'output': output}; + }); + }, + + /** + * Retrieve tag name + * @return string Tag name (DEFAULT: NULL) + */ + get_name: function() { + return this.get_attribute('name'); + }, + + /** + * Retrieve tag property + */ + get_prop: function() { + return this.get_attribute('prop'); + }, + + /** + * Retrieve tag handler + * @return Template_Tag_Handler Handler instance (Empty instance if handler does not exist) + */ + get_handler: function() { + return ( this.has_handler() ) ? this.handlers[this.get_name()] : new View.Template_Tag_Handler(''); + }, + + /** + * Check if handler exists for tag + * @return bool TRUE if handler exists, FALSE otherwise + */ + has_handler: function() { + return ( this.get_name() in this.handlers ); + }, + + /** + * Generate class names for DOM node + * @param string rtype (optional) Return data type + * > Default: array + * > If string supplied: Joined classes delimited by parameter + * @uses get_class() to generate class names + * @uses Array.join() to convert class names array to string + * @return array Class names + */ + get_classes: function(rtype) { + //Build array of class names + var cls = [ + //General tag class + this.get_class(), + //Tag name + this.get_class('tag'), + //Tag name + property + this.get_class('full') + ]; + //Convert class names array to string + if ( this.util.is_string(rtype) ) { + cls = cls.join(rtype); + } + //Return class names + return cls; + }, + + /** + * Generate DOM-compatible class name based with varied levels of specificity + * @param int level (optional) Class name specificity + * > Default: General tag class (common to all tag elements) + * > tag: Tag Name + * > full: Tag Name + Property + * @return string Class name + */ + get_class: function(level) { + var cls = ''; + switch ( level ) { + case 'tag' : + //Tag name + cls = this.add_ns(this.get_name()); + break; + case 'full' : + //Tag name + property + cls = this.add_ns([this.get_name(), this.get_prop()].join('_')); + break; + default : + //General + cls = this.get_ns(); + break; + } + return cls; + }, + + /** + * Generate tag selector based on specified class name level + * @param string level (optional) Class name specificity (@see get_class() for parameter values) + * @return string Tag selector + */ + get_selector: function(level) { + return '.' + this.get_class(level); + } +}; + +View.Template_Tag = Component.extend(Template_Tag); + +/** + * Theme tag handler + */ +var Template_Tag_Handler = { + /* Configuration */ + _slug: 'template_tag_handler', + /* Properties */ + _attr_default: { + supports_modifiers: false, + dynamic: false, + props: {} + }, + + /* Methods */ + + /** + * Render tag output + * @param Content_Item item Item currently being displayed + * @param Template_Tag Tag instance (from template) + * @return obj jQuery.Promise linked to rendering process + */ + render: function(item, instance) { + var dfr = $.Deferred(); + //Pass to attribute method + var ret = this.call_attribute('render', item, instance); + //Check for promise + if ( this.util.is_promise(ret) ) { + ret.done(function(output) { + dfr.resolve(output); + }); + } else { + //Resolve non-promises immediately + dfr.resolve(ret); + } + //Return promise + return dfr.promise(); + }, + + add_prop: function(prop, fn) { + //Get attribute + var a = 'props'; + var props = this.get_attribute(a); + //Validate + if ( !this.util.is_string(prop) || !this.util.is_func(fn) ) { + return false; + } + if ( !this.util.is_obj(props, false) ) { + props = {}; + } + //Add property + props[prop] = fn; + //Save attribute + this.set_attribute(a, props); + }, + + handle_prop: function(prop, item, instance) { + //Locate property + var props = this.get_attribute('props'); + var out = ''; + if ( this.util.is_obj(props) && ( prop in props ) && this.util.is_func(props[prop]) ) { + out = props[prop].call(this, item, instance); + } else { + out = item.get_viewer().get_label(prop); + } + return out; + } +}; + +View.Template_Tag_Handler = Component.extend(Template_Tag_Handler); +/* Update References */ + +//Attach to global object +SLB.attach('View', View); +View = SLB.View; +View.update_refs(); })(jQuery);} \ No newline at end of file diff --git a/client/sass/app.scss b/client/sass/app.scss index 0b18c47..a6043d0 100644 --- a/client/sass/app.scss +++ b/client/sass/app.scss @@ -2,4 +2,9 @@ html.slb_overlay { object,embed,iframe { visibility: hidden; } -} + #slb_viewer_wrap { + object,embed,iframe { + visibility: visible; + } + } +} \ No newline at end of file diff --git a/content-handlers/image/handler.image.js b/content-handlers/image/handler.image.js index 315ca94..9087bd4 100644 --- a/content-handlers/image/handler.image.js +++ b/content-handlers/image/handler.image.js @@ -3,8 +3,13 @@ $(document).ready(function() { if ( typeof SLB == 'undefined' || typeof SLB.View == 'undefined' || typeof SLB.View.extend_content_handler == 'undefined' ) return false; SLB.View.extend_content_handler('image', { - render: function(item) { - var dfr = $.Deferred(); + /** + * Render images + * @param obj item Content Item + * @param obj dfr Promise for rendering process + * @return obj Promise for rendering process (Resolved when content is loaded) + */ + render: function(item, dfr) { //Create image object var img = new Image(); var type = this; diff --git a/controller.php b/controller.php index cd456d4..4668b16 100644 --- a/controller.php +++ b/controller.php @@ -14,13 +14,13 @@ class SLB_Lightbox extends SLB_Base { var $scripts = array ( 'core' => array ( - 'file' => 'client/js/lib.core.js', + 'file' => 'client/js/dev/lib.core.js', 'deps' => 'jquery', 'enqueue' => false, 'in_footer' => true, ), 'view' => array ( - 'file' => 'client/js/lib.view.js', + 'file' => 'client/js/dev/lib.view.js', 'deps' => array('jquery', '[core]'), 'context' => array( array('public', '[is_request_valid]') ), 'in_footer' => true, @@ -122,6 +122,11 @@ function __construct() { /* Init */ + public function _init() { + parent::_init(); + $this->util->do_action('init'); + } + /** * Register hooks * @uses parent::_hooks() @@ -417,7 +422,8 @@ function process_links($content, $group = null) { } //Process links $protocol = array('http://', 'https://'); - $domain = str_replace($protocol, '', strtolower(get_bloginfo('url'))); + $uri_home = strtolower(home_url()); + $domain = str_replace($protocol, '', $uri_home); $qv_att = 'attachment_id'; //Setup group properties @@ -438,20 +444,23 @@ function process_links($content, $group = null) { } //Iterate through and activate supported links + $uri_proto = array('raw' => '', 'source' => ''); + foreach ( $links as $link ) { //Init vars $pid = 0; $link_new = $link; $internal = false; $q = null; - $uri = (object) array('raw' => '', 'base' => '', 'source' => ''); + $uri = (object) $uri_proto; $type = false; + $props_extra = array(); //Parse link attributes $attrs = $this->util->parse_attribute_string($link_new, array('href' => '')); $attrs_legacy = ( isset($attrs['rel']) && !empty($attrs['rel']) ) ? explode(' ', trim($attrs['rel'])) : array(); //Get URI - $uri->raw = $uri->base = $uri->source = $attrs['href']; + $uri->raw = $uri->source = $attrs['href']; //Stop processing invalid links if ( !$this->validate_uri($uri->raw) @@ -476,51 +485,47 @@ function process_links($content, $group = null) { } //Check if item links to internal media (attachment) - $uri_dom = str_replace($protocol, '', strtolower($uri->raw)); - if ( strpos($uri_dom, $domain) === 0 ) { - //Save URL for further processing + if ( 0 === strpos($uri->raw, '/') ) { + //Relative URIs are always internal $internal = true; - } - - //Sanitize URI - $qpos = strpos($uri->raw, '?'); - if ( $qpos !== false ) { - $uri->base = substr($uri->raw, 0, $qpos); - if ( $internal ) { - //Extract query string - $q = substr($uri->raw, $qpos + 1); - //Check for attachment ID - if ( strpos($q, $qv_att . '=') !== false ) { - //Parse query string - wp_parse_str($q, $q); - //Strip other variables from query string - $uri->base = add_query_arg($qv_att, $q[$qv_att], $uri->base); - } + $uri->source = $uri_home . $uri->raw; + } else { + //Absolute URI + $uri_dom = str_replace($protocol, '', strtolower($uri->raw)); + if ( strpos($uri_dom, $domain) === 0 ) { + $internal = true; } + unset($uri_dom); } - - //Get source URI - $uri->source = $uri->base; + + //Get source URI (e.g. attachments) if ( $internal && is_local_attachment($uri->source) ) { - $pid = url_to_postid($uri->base); + $pid = url_to_postid($uri->source); $src = wp_get_attachment_url($pid); if ( !!$src ) { $uri->source = $src; + $props_extra['id'] = $pid; } unset($src); } - /* Determine link type */ + /* Determine content type */ //Check if URI has already been processed - if ( $this->media_item_cached($uri->base) ) { - $i = $this->get_cached_media_item($uri->base); + if ( $this->media_item_cached($uri->source) ) { + $i = $this->get_cached_media_item($uri->source); $type = $i->type; } else { //Get handler match - $handler = $this->handlers->match($uri->source); - if ( !!$handler ) { - $type = $handler->get_id(); + $hdl_result = $this->handlers->match($uri->source); + if ( !!$hdl_result->handler ) { + $type = $hdl_result->handler->get_id(); + $props_extra = $hdl_result->props; + //Updated source URI + if ( isset($props_extra['uri']) ) { + $uri->source = $props_extra['uri']; + unset($props_extra['uri']); + } } } @@ -565,7 +570,7 @@ function process_links($content, $group = null) { } //Cache item attributes - $this->cache_media_item($uri, $type, $internal, $pid); + $this->cache_media_item($uri, $type, $internal, $props_extra); //Update link in content $link_new = 'util->build_attribute_string($attrs) . '>'; @@ -684,10 +689,7 @@ function client_footer() { add_action('wp_print_footer_scripts', $this->m('client_footer_script')); //Build client output - - echo '' . PHP_EOL; $this->util->do_action('footer'); - echo PHP_EOL . '' . PHP_EOL; } /** @@ -716,12 +718,13 @@ function client_script_media($client_script) { $props_map = array('description' => 'post_content', 'title' => 'post_title', 'caption' => 'post_excerpt'); //Separate media into buckets by type - $m_bucket = array(); + //$m_bucket = array(); $m_internals = array(); $type = $id = null; $m_items = $this->media_items = $this->get_cached_media_items(); foreach ( $m_items as $uri => $p ) { + /* $type = $p->{$props->type}; //Initialize bucket (if necessary) if ( !isset($m_bucket[$type]) ) { @@ -729,6 +732,7 @@ function client_script_media($client_script) { } //Add item to bucket $m_bucket[$type][$uri] =& $m_items[$uri]; + */ //Set aside internal links for additional processing if ( $p->internal && !isset($m_internals[$uri]) ) { $m_internals[$uri] =& $m_items[$uri]; @@ -846,6 +850,20 @@ function client_script_media($client_script) { } unset($atts, $atts_meta, $m, $a, $uri, $pids, $pids_flat); } + + //Expand URI variants + foreach ( $m_items as $uri => $p ) { + if ( empty($p->_entries) ) { + continue; + } + foreach ( $p->_entries as $uri_variant ) { + if ( isset($this->media_items[$uri_variant]) ) { + continue; + } + $this->media_items[$uri_variant] = array('_parent' => $uri); + } + } + unset($uri, $p, $uri_variant); //Build client output $obj = 'View.assets'; @@ -860,30 +878,29 @@ function client_script_media($client_script) { * @param object $uri URI to cache * Members * > raw: Raw Link URI - * > base: Sanitized URI * > source: Source URI (e.g. for attachment URIs) * @param string $type Media type (image, attachment, etc.) - * @param int $id (optional) ID of media item (for internal items) (Default: NULL) + * @param bool $internal TRUE if media is internal (e.g. attachment) + * @param array $props (optional) Properties to store for item (Default: NULL) */ - private function cache_media_item($uri, $type, $internal, $id = null) { + private function cache_media_item($uri, $type, $internal, $props = null) { //Validate if ( !is_object($uri) || !is_string($type) ) { return false; } - if ( !$this->media_item_cached($uri->base) ) { + if ( !$this->media_item_cached($uri->source) ) { //Set properties - $i = array('type' => $type, 'source' => $uri->source, 'internal' => $internal, 'id' => null, '_entries' => array()); - //ID - if ( is_numeric($id) && !!$id ) { - $i['id'] = absint($id); + $i = array('id' => null, '_entries' => array()); + if ( is_array($props) && !empty($props) ) { + $i = array_merge($i, $props); } + $i = array_merge($i, array('type' => $type, 'source' => $uri->source, 'internal' => $internal)); //Cache media item - $this->media_items_raw[$uri->base] = (object) $i; + $this->media_items_raw[$uri->source] = (object) $i; } //Add URI variants - $entries =& $this->media_items_raw[$uri->base]->_entries; - if ( !in_array($uri->raw, $entries) ) { - $entries[] = $uri->raw; + if ( $uri->raw != $uri->source && !in_array($uri->raw, $this->media_items_raw[$uri->source]->_entries) ) { + $this->media_items_raw[$uri->source]->_entries[] = $uri->raw; } } diff --git a/includes/class.admin.php b/includes/class.admin.php index a256794..bdacf77 100644 --- a/includes/class.admin.php +++ b/includes/class.admin.php @@ -15,7 +15,7 @@ class SLB_Admin extends SLB_Base { protected $scripts = array ( 'admin' => array ( - 'file' => 'client/js/lib.admin.js', + 'file' => 'client/js/dev/lib.admin.js', 'deps' => array('[core]'), 'context' => array( 'admin_page_slb' ), 'in_footer' => true, @@ -542,13 +542,14 @@ public function plugin_action_links($actions, $plugin_file, $plugin_data, $conte * @return array Updated plugin metadata */ public function plugin_row_meta($plugin_meta, $plugin_file, $plugin_data, $status) { - if ( $plugin_file == $this->util->get_plugin_base_name() ) { + $u = ( is_object($this->parent) && isset($this->parent->util) ) ? $this->parent->util : $this->util; + if ( $plugin_file == $u->get_plugin_base_name() ) { //Add metadata //Support $t = __('Get Support', 'simple-lightbox'); - $l = $this->util->get_plugin_info('SupportURI'); + $l = $u->get_plugin_info('SupportURI'); if ( !empty($l) ) { - $plugin_meta[] = $this->util->build_html_link($l, $t); + $plugin_meta[] = $u->build_html_link($l, $t); } } return $plugin_meta; diff --git a/includes/class.base.php b/includes/class.base.php index 88219e5..d9eac88 100644 --- a/includes/class.base.php +++ b/includes/class.base.php @@ -170,7 +170,7 @@ private function _env() { $lpath = $this->util->get_plugin_file_path($ldir, array(false, false)); $lpath_abs = $this->util->get_file_path($ldir); if ( is_dir($lpath_abs) ) { - load_plugin_textdomain('ar-series', false, $lpath); + load_plugin_textdomain('simple-lightbox', false, $lpath); } //Context diff --git a/includes/class.base_object.php b/includes/class.base_object.php index 6a63208..14c8533 100644 --- a/includes/class.base_object.php +++ b/includes/class.base_object.php @@ -251,11 +251,8 @@ protected function get_file($type, $handle, $format = null) { if ( !$this->util->is_uri($ret) ) { $ret = $this->util->normalize_path(site_url(), $ret); } - $ch = curl_init($ret); - curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); - curl_setopt($ch, CURLOPT_HEADER, false); - $ret = curl_exec($ch); - curl_close($ch); + $get = wp_safe_remote_get($ret); + $ret = ( !is_wp_error($get) && 200 == $get['response']['code'] ) ? $get['body'] : ''; break; } } diff --git a/includes/class.content_handler.php b/includes/class.content_handler.php index 38591d8..306b384 100644 --- a/includes/class.content_handler.php +++ b/includes/class.content_handler.php @@ -15,6 +15,12 @@ class SLB_Content_Handler extends SLB_Component { */ protected $match; + /** + * Custom attributes + * @var callback + */ + protected $attributes; + /* Matching */ /** @@ -47,11 +53,30 @@ protected function has_match() { * @param string $uri URI to check for match * @return bool TRUE if handler matches URI */ - public function match($uri) { + public function match($uri, $uri_raw = null) { $ret = false; if ( !!$uri && is_string($uri) && $this->has_match() ) { - $ret = call_user_func($this->get_match(), $uri); + $ret = call_user_func($this->get_match(), $uri, $uri_raw); } return $ret; } + + /* Attributes */ + + public function set_attributes($callback) { + $this->attributes = ( is_callable($callback) ) ? $callback : null; + return $this; + } + + public function get_attributes() { + $ret = array(); + //Callback + if ( !is_null($this->attributes) ) { + $ret = call_user_func($this->attributes); + } + //Filter + $hook = sprintf('content_handler_%s_attributes', $this->get_id()); + $ret = $this->util->apply_filters($hook, $ret); + return ( is_array($ret) ) ? $ret : array(); + } } \ No newline at end of file diff --git a/includes/class.content_handlers.php b/includes/class.content_handlers.php index 0b2576b..6e74d31 100644 --- a/includes/class.content_handlers.php +++ b/includes/class.content_handlers.php @@ -31,7 +31,7 @@ class SLB_Content_Handlers extends SLB_Collection_Controller { protected function _hooks() { parent::_hooks(); - $this->util->add_action('init', $this->m('init_defaults')); + $this->util->add_action('init', $this->m('init_defaults'), 5); $this->util->add_action('footer', $this->m('client_output'), 1, 0, false); $this->util->add_filter('footer_script', $this->m('client_output_script'), $this->util->priority('client_footer_output'), 1, false); } @@ -107,20 +107,30 @@ public function get() { /** * Get matching handler for URI * @param string $uri URI to find match for - * @return SLB_Content_Handler Matching handler (NULL if no handler matched) + * @return object Handler package (FALSE if no match found) + * Package members + * > handler (Content_Handler) Matching handler instance (Default: NULL) + * > props (array) Properties returned from matching handler (May be empty depending on handler) */ public function match($uri) { + $ret = (object) array('handler' => null, 'props' => array()); foreach ( $this->get() as $handler ) { - if ( $handler->match($uri) ) { + $props = $handler->match($uri, $this); + if ( !!$props ) { + $ret->handler = $handler; + //Add handler props + if ( is_array($props) ) { + $ret->props = $props; + } //Save match $hid = $handler->get_id(); if ( !isset($this->request_matches[$hid]) ) { $this->request_matches[$hid] = $handler; } - return $handler; + break; } } - return null; + return $ret; } /* Cache */ @@ -197,16 +207,26 @@ public function init_defaults($handlers) { * @param string $uri URI to match * @return bool TRUE if URI is image */ - public function match_image($uri) { + public function match_image($uri, $handlers) { + //Sanitize URI + $qpos = strpos($uri, '?'); + $uri_source = ( $qpos !== false ) ? substr($uri, 0, $qpos) : $uri; + //Standard - $match = ( $this->util->has_file_extension($uri, array('jpg', 'jpeg', 'jpe', 'jfif', 'jif', 'gif', 'png')) ) ? true : false; + $match = ( $this->util->has_file_extension($uri_source, array('jpg', 'jpeg', 'jpe', 'jfif', 'jif', 'gif', 'png')) ) ? true : false; //If match not found, allow third-party matching if ( !$match ) { $match = $this->util->apply_filters('image_match', $match, $uri); } - return !!$match; + if ( !!$match ) { + $ret = ( $uri != $uri_source ) ? array('uri' => $uri_source) : true; + } else { + $ret = false; + } + + return $ret; } /* Output */ @@ -232,15 +252,21 @@ public function client_output_script($commands) { $code = array(); foreach ( $this->request_matches as $handler ) { + //Attributes + $attrs = $handler->get_attributes(); + //Styles $styles = $handler->get_styles(array('uri_format'=>'full')); - if ( empty($styles) ) { + if ( !empty($styles) ) { + $attrs['styles'] = array_values($styles); + } + if ( empty($attrs) ) { continue; } //Setup client parameters $params = array( sprintf("'%s'", $handler->get_id()), + json_encode($attrs), ); - $params[] = json_encode( array('styles' => array_values($styles)) ); //Extend handler in client $code[] = $this->util->call_client_method('View.extend_content_handler', $params, false); } diff --git a/includes/class.utilities.php b/includes/class.utilities.php index cb623ed..4619143 100644 --- a/includes/class.utilities.php +++ b/includes/class.utilities.php @@ -16,31 +16,40 @@ class SLB_Utilities { * Instance parent * @var object */ - var $parent = null; + private $_parent = null; + + /** + * Plugin Base + * @var string + */ + private $_plugin = array( + 'base' => null, + 'file' => null, + 'name' => null, + 'data' => null, + 'uri' => null, + 'headers' => array ( + 'Name' => 'Plugin Name', + 'PluginURI' => 'Plugin URI', + 'SupportURI' => 'Support URI', + 'Version' => 'Version', + 'Description' => 'Description', + 'Author' => 'Author', + 'AuthorURI' => 'Author URI', + ) + ); /** - * Default plugin headers - * @var array + * Plugin base path + * @var string */ - private $plugin_headers = array ( - 'Name' => 'Plugin Name', - 'PluginURI' => 'Plugin URI', - 'SupportURI' => 'Support URI', - 'Version' => 'Version', - 'Description' => 'Description', - 'Author' => 'Author', - 'AuthorURI' => 'Author URI', - 'TextDomain' => 'Text Domain', - 'DomainPath' => 'Domain Path', - 'Network' => 'Network', - ); - + private $_path_base = null; /** * Standard hook priorities * @var array */ - private $priorities = array ( + private $_priorities = array ( 'high' => 1, 'low' => 99, 'safe' => 15, @@ -50,8 +59,9 @@ class SLB_Utilities { /* Constructors */ function __construct(&$obj) { - if ( is_object($obj) ) - $this->parent =& $obj; + if ( is_object($obj) ) { + $this->_parent =& $obj; + } } /** @@ -94,7 +104,7 @@ function get_sep($sep = false) { */ function get_prefix($sep = null) { $sep = $this->get_sep($sep); - $prefix = ( !empty($this->parent->prefix) ) ? $this->parent->prefix . $sep : ''; + $prefix = ( !empty($this->_parent->prefix) ) ? $this->_parent->prefix . $sep : ''; return $prefix; } @@ -189,8 +199,8 @@ function get_db_prefix() { */ public function priority($id = null) { $pri = 10; - if ( !is_null($id) && array_key_exists($id, $this->priorities) ) { - $pri = $this->priorities[$id]; + if ( !is_null($id) && array_key_exists($id, $this->_priorities) ) { + $pri = $this->_priorities[$id]; } return $pri; } @@ -427,7 +437,7 @@ function parse_client_files($files, $type = 'scripts') { */ function parse_client_file_callback($callback) { if ( $this->has_wrapper($callback) ) { - $callback = $this->m($this->parent, $this->remove_wrapper($callback)); + $callback = $this->m($this->_parent, $this->remove_wrapper($callback)); } if ( !is_callable($callback) ) $callback = null; @@ -590,11 +600,12 @@ function check_post(&$post) { * Retrieve parent object * @return obj|bool Parent object (FALSE if no valid parent set) */ - function &get_parent() { - if ( is_object($this->parent) ) - return $this->parent; - else - return false; + function get_parent() { + if ( is_object($this->_parent) ) { + return $this->_parent; + } else { + return false; + } } /** @@ -1104,11 +1115,10 @@ function strip_file_extension($file) { * @return string Base URL */ function get_url_base($trailing_slash = false, $relative = null) { - static $url_base = null; - if ( empty($url_base) ) { - $url_base = $this->normalize_path(plugins_url(), $this->get_plugin_base()); + $ret = $this->_plugin['uri']; + if ( empty($ret) ) { + $ret = $this->normalize_path(plugins_url(), $this->get_plugin_base()); } - $ret = $url_base; //Trailing slash if ( !!$trailing_slash ) { $ret .= '/'; @@ -1136,11 +1146,24 @@ function get_url_base($trailing_slash = false, $relative = null) { * @return string Base path */ function get_path_base($relative = null) { - static $path_base = ''; - if ( '' == $path_base ) { - $path_base = $this->normalize_path(WP_PLUGIN_DIR, $this->get_plugin_base()); + $ret = $this->_path_base; + if ( empty($ret) ) { + //Get base directory of parent object + if ( $this->get_parent() ) { + $r = new ReflectionClass(get_class($this->get_parent())); + $base = $r->getFileName(); + unset($r); + } else { + $base = __FILE__; + } + //Extract base path + $base = $this->normalize_path($base); + if ( 0 === strpos($base, $this->normalize_path(WP_PLUGIN_DIR)) ) { + $end = strpos($base, '/', strlen(WP_PLUGIN_DIR) + 1); + $base = substr($base, 0, $end); + } + $ret = $this->_path_base = $base; } - $ret = $path_base; //Make relative path if ( !empty($relative) ) { //Default @@ -1180,17 +1203,16 @@ function get_relative_path($path, $relative = true) { /** * Retrieve plugin's base directory * @uses WP_PLUGIN_DIR - * @uses normalize_path() + * @uses Utilities::get_path_base() to retrieve plugin base path + * @uses Utilities::_plugin_base to save plugin base * @return string Base directory */ - function get_plugin_base($trim = false) { - static $plugin_dir = ''; - if ( '' == $plugin_dir ) { - $plugin_dir = str_replace($this->normalize_path(WP_PLUGIN_DIR), '', $this->normalize_path(dirname(dirname(__FILE__)))); + function get_plugin_base() { + $ret = $this->_plugin['base']; + if ( empty($ret) ) { + $ret = $this->_plugin['base'] = basename($this->get_path_base()); } - if ( $trim ) - $plugin_dir = trim($plugin_dir, ' \/'); - return $plugin_dir; + return $ret; } /** @@ -1200,9 +1222,9 @@ function get_plugin_base($trim = false) { * @return string Base file path */ function get_plugin_base_file() { - static $file = ''; - if ( empty($file) ) { - $dir = @ opendir($this->get_path_base()); + $ret = $this->_plugin['file']; + if ( empty($ret) ) { + $dir = @opendir($this->get_path_base()); if ( $dir ) { while ( ($ftemp = readdir($dir)) !== false ) { //Only process PHP files @@ -1210,10 +1232,12 @@ function get_plugin_base_file() { if ( !$this->has_file_extension($ftemp, 'php') || !is_readable($ftemp) ) continue; //Check for data - $data = get_file_data($ftemp, $this->plugin_headers); + $data = get_file_data($ftemp, $this->_plugin['headers']); if ( !empty($data['Name']) ) { //Set base file $file = $ftemp; + //Save plugin data + $this->set_plugin_info($data); break; } } @@ -1232,33 +1256,35 @@ function get_plugin_base_file() { * @return string Internal plugin name */ function get_plugin_base_name() { - static $name = false; - if ( !$name ) { - $file = $this->get_plugin_base_file(); - $name = plugin_basename($file); + $ret = $this->_plugin['name']; + if ( empty($ret) ) { + $ret = $this->_plugin['name'] = plugin_basename( $this->get_plugin_base_file() ); + } + return $ret; + } + + private function set_plugin_info($data) { + if ( is_array($data) ) { + $this->_plugin['data'] = $data; } - return $name; } /** * Retrieve plugin info * Parses info comment in main plugin file - * @uses get_plugin_base_file() + * @uses get_plugin_base_file() to retrieve plugin info + * @return array|string Plugin info (specific value if field set) */ - function get_plugin_info($field = '') { - static $data = array(); - $ret = ''; + function get_plugin_info($field = null) { + $ret = $this->_plugin['data']; //Get plugin data - if ( empty($data) ) { - $file = $this->get_plugin_base_file(); - $data = get_file_data($file, $this->plugin_headers); + if ( empty($ret) ) { + $this->get_plugin_base_file(); + $ret = $this->_plugin['data']; } //Return specified field if ( !empty($field) ) { - if ( isset($data[$field]) ) - $ret = $data[$field]; - } else { - $ret = $data; + $ret = ( is_array($ret) && isset($ret[$field]) ) ? $ret[$field] : ''; } return $ret; } @@ -1270,15 +1296,10 @@ function get_plugin_info($field = '') { * @return string Plugin version */ function get_plugin_version($strip_desc = true) { - static $v = ''; //Retrieve version - if ( empty($v) ) { - $field = 'Version'; - $v = $this->get_plugin_info($field); - } + $ret = $this->get_plugin_info('Version'); //Format - $ret = $v; - if ( $strip_desc ) { + if ( !empty($ret) && $strip_desc ) { $ret = explode(' ', $ret, 2); $ret = $ret[0]; } @@ -1286,17 +1307,6 @@ function get_plugin_version($strip_desc = true) { return $ret; } - /** - * Retrieve plugin textdomain (for localization) - * @return string - */ - function get_plugin_textdomain() { - static $dom = ''; - if ( empty($dom) ) - $dom = $this->get_plugin_base(true); - return $dom; - } - /** * Retrieve current post type based on URL query variables * @return string|null Current post type @@ -1350,25 +1360,9 @@ function get_action($default = null) { /*-** General **-*/ - /** - * Checks if last parameter sent to a function is an array of options and returns it - * Calling function should use `func_get_args()` and pass the value to this method - * @param array $args Parameters passed to calling function - * @return array Options array (Default: empty array) - */ - function func_get_options($args) { - $r = array(); - if ( is_array($args) && !empty($args) ) { - $last = count($args) - 1; - if ( is_array($args[$last]) ) - $r = $args[$last]; - } - return $r; - } - /** * Checks if a property exists in a class or object - * (Compatibility method for PHP 4 + * Compatibility method for PHP 4 * @param mixed $class Class or object to check * @param string $property Name of property to look for in $class */ @@ -1543,20 +1537,6 @@ function array_item_isset(&$arr, &$path) { return eval('return isset($arr' . $f_path . ');'); } - /** - * Returns value of item at specified path in array - * @param array $arr Array to get item from - * @param array $path Array of segments that form path to array (each array item is a deeper dimension in the array) - * @return mixed Value of item in array (Default: empty string) - */ - function &get_array_item(&$arr, &$path) { - $item = ''; - if ($this->array_item_isset($arr, $path)) { - eval('$item =& $arr' . $this->get_array_path($path) . ';'); - } - return $item; - } - /** * Build formatted string based on array values * Array values in formatted string will be ordered by index order @@ -1677,6 +1657,14 @@ function build_attribute_string($attr) { return $ret; } + /** + * Build HTML link element + * @uses build_html_element() to build link output + * @param string $uri Link URI + * @param string $content Link content + * @param $array (optional) $attributes Additional link attributes + * @return string HTML link element + */ function build_html_link($uri, $content, $attributes = array()) { $attributes = array_merge(array('href' => $uri, 'title' => $content), $attributes); return $this->build_html_element(array('tag' => 'a', 'wrap' => true, 'content' => $content, 'attributes' => $attributes)); @@ -1731,16 +1719,6 @@ function build_script_element($content = '', $id = '', $wrap_jquery = true, $wai return $this->build_html_element(array('tag' => 'script', 'content' => $content, 'attributes' => $attributes)) . PHP_EOL; } - /** - * Generate external script element - * @param $url Script URL - * @return string Script element - */ - function build_ext_script_element($url = '') { - $attributes = array('src' => $url, 'type' => 'text/javascript'); - return $this->build_html_element(array('tag' => 'script', 'attributes' => $attributes)) . PHP_EOL; - } - /** * Generate HTML element based on values * @param $args Element arguments @@ -1787,194 +1765,4 @@ function build_html_element($args) { $ret .= $el_end; return $ret; } - - /*-** Admin **-*/ - - /** - * Add submenu page in the admin menu - * Adds ability to set the position of the page in the menu - * @see add_submenu_page (Wraps functionality) - * - * @param $parent - * @param $page_title - * @param $menu_title - * @param $access_level - * @param $file - * @param $function - * @param int $pos Index position of menu page - * - * @global array $submenu Admin page submenus - */ - function add_submenu_page($parent, $page_title, $menu_title, $capability, $file, $function = '', $pos = false) { - //Add submenu page as usual - $args = func_get_args(); - $hookname = call_user_func_array('add_submenu_page', $args); - if ( is_int($pos) ) { - global $submenu; - //Get last submenu added - $parent = $this->get_submenu_parent_file($parent); - if ( isset($submenu[$parent]) ) { - $subs =& $submenu[$parent]; - - //Make sure menu isn't already in the desired position - if ( $pos <= ( count($subs) - 1 ) ) { - //Get submenu that was just added - $sub = array_pop($subs); - //Insert into desired position - if ( 0 == $pos ) { - array_unshift($subs, $sub); - } else { - $top = array_slice($subs, 0, $pos); - $bottom = array_slice($subs, $pos); - array_push($top, $sub); - $subs = array_merge($top, $bottom); - } - } - } - } - - return $hookname; - } - - /** - * Remove admin submenu - * @param string $parent Submenu parent file - * @param string $file Submenu file name - * @return int|null Index of removed submenu (NULL if submenu not found) - * - * @global array $submenu - * @global array $_registered_pages - */ - function remove_submenu_page($parent, $file) { - global $submenu, $_registered_pages; - $ret = null; - - $parent = $this->get_submenu_parent_file($parent); - $file = plugin_basename($file); - $file_index = 2; - - //Find submenu - if ( isset($submenu[$parent]) ) { - $subs =& $submenu[$parent]; - for ($x = 0; $x < count($subs); $x++) { - if ( $subs[$x][$file_index] == $file ) { - //Remove matching submenu - $hookname = get_plugin_page_hookname($file, $parent); - remove_all_actions($hookname); - unset($_registered_pages[$hookname]); - unset($subs[$x]); - $subs = array_values($subs); - //Set index and stop processing - $ret = $x; - break; - } - } - } - - return $ret; - } - - /** - * Replace a submenu page - * Adds a submenu page in the place of an existing submenu page that has the same $file value - * - * @param $parent - * @param $page_title - * @param $menu_title - * @param $access_level - * @param $file - * @param $function - * @return string Hookname - * - * @global array $submenu - */ - function replace_submenu_page($parent, $page_title, $menu_title, $access_level, $file, $function = '') { - global $submenu; - //Remove matching submenu (if exists) - $pos = $this->remove_submenu_page($parent, $file); - //Insert submenu page - $hookname = $this->add_submenu_page($parent, $page_title, $menu_title, $access_level, $file, $function, $pos); - return $hookname; - } - - /** - * Retrieves parent file for submenu - * @param string $parent Parent file - * @return string Formatted parent file name - * - * @global array $_wp_real_parent_file; - */ - function get_submenu_parent_file($parent) { - global $_wp_real_parent_file; - $parent = plugin_basename($parent); - if ( isset($_wp_real_parent_file[$parent]) ) - $parent = $_wp_real_parent_file[$parent]; - return $parent; - } - - /* Shortcodes */ - - /** - * Generate shortcode to be used in content - * @param string $tag Shortcode tag - * @param array $attr Associative array of attributes - * @return string Shortcode markup - */ - public function make_shortcode($tag, $attr = array()) { - return '[' . $tag . ']'; - } - - /** - * Build shortcode regex pattern for specific shortcode - * @uses $shortcode_tags - * @param string $tag Shortcode tag - * @return string Shortcode regex pattern - */ - public function get_shortcode_regex($tag) { - global $shortcode_tags; - //Backup shortcodes - $tgs_temp = $shortcode_tags; - $ret = ''; - if ( !is_string($tag) || empty($tag) ) { - return $ret; - } - //Modify - $shortcode_tags = array( $tag => null ); - //Build pattern - $ret = get_shortcode_regex(); - //Restore shortcodes - $shortcode_tags = $tgs_temp; - - return $ret; - } - /** - * Check if content contains shortcode - * @param string $tag Name of shortcode to check for - * @param string $content Content to check for shortcode - * @return bool TRUE if content contains shortcode - */ - public function has_shortcode($content, $tag) { - $ptn = $this->get_shortcode_regex($tag); - $ret = ( is_string($content) && preg_match("/$ptn/s", $content) == 1 ) ? true : false; - return $ret; - } - - /** - * Add shortcode to content - * @param string $content Content to add shortcode to - * @param bool $in_footer (optional) Add shortcode to head or footer of content (Default: footer) - * @return string Modified content - */ - public function add_shortcode($content, $tag, $attr = null, $in_footer = true) { - if ( !is_string($content) ) { - $content = ''; - } - $sc = $this->make_shortcode($tag, $attr); - if ( !!$in_footer ) { - $content .= $sc; - } else { - $content = $sc . $content; - } - return $content; - } } \ No newline at end of file diff --git a/main.php b/main.php index bcdacc0..82d053c 100644 --- a/main.php +++ b/main.php @@ -3,7 +3,7 @@ Plugin Name: Simple Lightbox Plugin URI: http://archetyped.com/tools/simple-lightbox/ Description: The highly customizable lightbox for WordPress -Version: 2.1.3 +Version: 2.2.0 (BETA 1) Author: Archetyped Author URI: http://archetyped.com Support URI: https://github.com/archetyped/simple-lightbox/wiki/Reporting-Issues @@ -16,8 +16,10 @@ * Initialize SLB */ function slb_init() { - require_once 'load.php'; - require_once 'controller.php'; + $path = dirname(__FILE__) . '/'; + require_once $path . 'load.php'; + require_once $path . 'controller.php'; $GLOBALS['slb'] = new SLB_Lightbox(); } + add_action('init', 'slb_init', 1); \ No newline at end of file diff --git a/package.json b/package.json new file mode 100644 index 0000000..f2765b7 --- /dev/null +++ b/package.json @@ -0,0 +1,20 @@ +{ + "name": "simple-lightbox", + "version": "2.2.0-beta1", + "title": "Simple Lightbox", + "description": "The highly-customizable lightbox for WordPress", + "author": "Sol Marchessault ", + "license": "GPLv2", + "private": true, + "devDependencies": { + "grunt": "latest", + "grunt-contrib-concat": "~0.3.0", + "grunt-contrib-uglify": "~0.2.7", + "grunt-contrib-qunit": "~0.3.0", + "grunt-contrib-jshint": "~0.7.2", + "grunt-contrib-watch": "~0.5.3", + "grunt-sass": "~0.8.1", + "node-bourbon": "~1.0.0", + "grunt-phplint": "0.0.5" + } +} diff --git a/readme.txt b/readme.txt index 46a6824..5ea78d8 100644 --- a/readme.txt +++ b/readme.txt @@ -56,6 +56,20 @@ Get more information on [Simple Lightbox's official page](http://archetyped.com/ 3. Dark Theme == Changelog == += 2.2.0 (Beta) = +* Add: Add-on support +* Add: Load external data for item +* Add: Unloading process for viewer +* Add: Relative links marked as "internal" +* Add: Grunt build workflow +* Optimize: Initialization process +* Optimize: Client-side output (JavaScript, CSS) +* Optimize: Improved URI handling (variants, query strings, etc.) +* Optimize: Improved support for content types (video, etc.) +* Optimize: Improved File contents retrieval +* Optimize: Plugin metadata cleanup +* Optimize: Use absolute paths for file includes (props k3davis) + = 2.1.3 = * Fix: PHP configuration issue on some web hosts (Tim's got (config) issues) * Optimize: Hide overlapping elements when lightbox is displayed (e.g. Flash, etc.) diff --git a/themes/baseline/client.js b/themes/baseline/client.js index 9919b9a..7b7635d 100644 --- a/themes/baseline/client.js +++ b/themes/baseline/client.js @@ -1,7 +1,10 @@ (function($) { $(document).ready(function() { -if ( typeof SLB == 'undefined' || typeof SLB.View == 'undefined' || typeof SLB.View.extend_theme == 'undefined' ) +//Validation +if ( typeof SLB === 'undefined' || typeof SLB.View === 'undefined' || typeof SLB.View.extend_theme !== 'function' ) { return false; +} +//Extend theme SLB.View.extend_theme('slb_baseline', { /** * Theme offsets diff --git a/themes/baseline/css/style.css b/themes/baseline/css/style.css index 1b19834..1dfc75c 100644 --- a/themes/baseline/css/style.css +++ b/themes/baseline/css/style.css @@ -1 +1 @@ -#slb_viewer_wrap .slb_theme_slb_baseline{position:absolute;top:0;left:0;width:100%;z-index:99999;text-align:center;line-height:0;color:#000;font-family:arial, verdana, sans-serif;font-size:12px}#slb_viewer_wrap .slb_theme_slb_baseline *{margin:0;padding:0;line-height:1.4em;text-align:left;vertical-align:baseline;white-space:normal;outline:none;border:0px;background:none;opacity:1;width:auto;height:auto;position:static;float:none;clear:none}#slb_viewer_wrap .slb_theme_slb_baseline a img{border:none}#slb_viewer_wrap .slb_theme_slb_baseline .slb_viewer_layout{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;z-index:2;position:absolute;width:100%;text-align:center}#slb_viewer_wrap .slb_theme_slb_baseline .slb_viewer_overlay{position:fixed;top:0;left:0;z-index:1;min-height:105%;min-width:100%;background-color:#000}#slb_viewer_wrap .slb_theme_slb_baseline .slb_container{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;position:relative;display:inline-block;background-color:#fff;margin:0 auto;padding:16px}#slb_viewer_wrap .slb_theme_slb_baseline .slb_loading{background:url("../images/loading.gif") center center no-repeat;position:absolute;left:0%;top:0;width:100%;height:100%;min-width:31px;min-height:31px;text-align:center;line-height:0;display:none}#slb_viewer_wrap .slb_theme_slb_baseline .slb_template_tag_ui{cursor:pointer}#slb_viewer_wrap .slb_theme_slb_baseline .slb_content{position:relative}#slb_viewer_wrap .slb_theme_slb_baseline .slb_details{margin:0 auto;text-align:left}#slb_viewer_wrap .slb_theme_slb_baseline .slb_details .inner{display:table;width:100%}#slb_viewer_wrap .slb_theme_slb_baseline .slb_details .slb_data{display:table-caption}#slb_viewer_wrap .slb_theme_slb_baseline .slb_template_tag_item_content>*{width:100%;height:100%}#slb_viewer_wrap .slb_theme_slb_baseline.item_single .slb_group_status,#slb_viewer_wrap .slb_theme_slb_baseline.item_single .slb_nav,#slb_viewer_wrap .slb_theme_slb_baseline.item_single .slb_slideshow{display:none}#slb_viewer_wrap .slb_theme_slb_baseline.loading .slb_loading{display:block}#slb_viewer_wrap .slb_theme_slb_baseline.loading .slb_template_tag_ui{filter:progid:DXImageTransform.Microsoft.Alpha(Opacity=0);opacity:0}@media screen and (max-width: 480px){#slb_viewer_wrap .slb_theme_slb_baseline{min-height:100%;min-width:320px;width:100%}#slb_viewer_wrap .slb_theme_slb_baseline .slb_viewer_layout{min-height:100%;min-width:320px;width:100%;display:block}#slb_viewer_wrap .slb_theme_slb_baseline .slb_container{min-height:100%;min-width:320px;width:100%;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;max-width:100%;margin:0;padding:5px;position:absolute;top:0;left:0}#slb_viewer_wrap .slb_theme_slb_baseline .slb_container .slb_content img,#slb_viewer_wrap .slb_theme_slb_baseline .slb_container .slb_content iframe,#slb_viewer_wrap .slb_theme_slb_baseline .slb_container .slb_content object,#slb_viewer_wrap .slb_theme_slb_baseline .slb_container .slb_content .slb_inner{max-width:100%}#slb_viewer_wrap .slb_theme_slb_baseline .slb_container .slb_content img{height:auto}} +#slb_viewer_wrap .slb_theme_slb_baseline .slb_viewer_layout ,#slb_viewer_wrap .slb_theme_slb_baseline .slb_container {-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;}#slb_viewer_wrap .slb_theme_slb_baseline {position:absolute;top:0;left:0;width:100%;z-index:99999;text-align:center;line-height:0;color:#000000;font-family:arial,verdana,sans-serif;font-size:12px;}#slb_viewer_wrap .slb_theme_slb_baseline * {margin:0;padding:0;line-height:1.4em;text-align:left;vertical-align:baseline;white-space:normal;outline:none;border:0px;background:none;opacity:1;width:auto;height:auto;position:static;float:none;clear:none;}#slb_viewer_wrap .slb_theme_slb_baseline a img {border:none;}#slb_viewer_wrap .slb_theme_slb_baseline .slb_viewer_layout {z-index:2;position:absolute;width:100%;text-align:center;}#slb_viewer_wrap .slb_theme_slb_baseline .slb_viewer_overlay {position:fixed;top:0;left:0;z-index:1;min-height:105%;min-width:100%;background-color:#000000;}#slb_viewer_wrap .slb_theme_slb_baseline .slb_container {position:relative;display:inline-block;background-color:#ffffff;margin:0 auto;padding:16px;}#slb_viewer_wrap .slb_theme_slb_baseline .slb_loading {background:url('../images/loading.gif') center center no-repeat;position:absolute;left:0%;top:0;width:100%;height:100%;min-width:31px;min-height:31px;text-align:center;line-height:0;display:none;}#slb_viewer_wrap .slb_theme_slb_baseline .slb_template_tag_ui {cursor:pointer;}#slb_viewer_wrap .slb_theme_slb_baseline .slb_content {position:relative;}#slb_viewer_wrap .slb_theme_slb_baseline .slb_details {margin:0 auto;text-align:left;}#slb_viewer_wrap .slb_theme_slb_baseline .slb_details .inner {display:table;width:100%;}#slb_viewer_wrap .slb_theme_slb_baseline .slb_details .slb_data {display:table-caption;}#slb_viewer_wrap .slb_theme_slb_baseline .slb_template_tag_item_content>* {width:100%;height:100%;}#slb_viewer_wrap .slb_theme_slb_baseline.item_single .slb_group_status ,#slb_viewer_wrap .slb_theme_slb_baseline.item_single .slb_nav ,#slb_viewer_wrap .slb_theme_slb_baseline.item_single .slb_slideshow {display:none;}#slb_viewer_wrap .slb_theme_slb_baseline.loading .slb_loading {display:block;}#slb_viewer_wrap .slb_theme_slb_baseline.loading .slb_template_tag_ui {opacity:0;}@media screen and (max-width: 480px){#slb_viewer_wrap .slb_theme_slb_baseline ,#slb_viewer_wrap .slb_theme_slb_baseline .slb_viewer_layout ,#slb_viewer_wrap .slb_theme_slb_baseline .slb_container {min-height:100%;min-width:320px;width:100%;}#slb_viewer_wrap .slb_theme_slb_baseline .slb_viewer_layout {display:block;}#slb_viewer_wrap .slb_theme_slb_baseline .slb_container {max-width:100%;margin:0;padding:5px;position:absolute;top:0;left:0;}#slb_viewer_wrap .slb_theme_slb_baseline .slb_container .slb_content img ,#slb_viewer_wrap .slb_theme_slb_baseline .slb_container .slb_content iframe ,#slb_viewer_wrap .slb_theme_slb_baseline .slb_container .slb_content object ,#slb_viewer_wrap .slb_theme_slb_baseline .slb_container .slb_content .slb_inner {max-width:100%;}#slb_viewer_wrap .slb_theme_slb_baseline .slb_container .slb_content img {height:auto;}} \ No newline at end of file diff --git a/themes/baseline/sass/style.scss b/themes/baseline/sass/style.scss index d5ab299..e94f2c1 100644 --- a/themes/baseline/sass/style.scss +++ b/themes/baseline/sass/style.scss @@ -1,12 +1,12 @@ //Imports -@import "compass"; +@import "bourbon"; //Variables //Mixins -@mixin box-sizing-border { +%box-sizing-border-box { @include box-sizing(border-box); } @@ -25,7 +25,7 @@ size: 12px; } - /* Reset */ + //Reset * { margin: 0; padding: 0; @@ -44,14 +44,14 @@ clear: none; } - /* General */ + //General a img { border: none; } .slb_viewer_layout { - @include box-sizing-border; + @extend %box-sizing-border-box; z-index: 2; position: absolute; width: 100%; @@ -69,7 +69,7 @@ } .slb_container { - @include box-sizing-border; + @extend %box-sizing-border-box; position: relative; display: inline-block; background-color: #fff; @@ -95,9 +95,9 @@ cursor: pointer; } - /* UI */ + //UI - /* Content */ + //Content .slb_content { position: relative; } @@ -135,7 +135,7 @@ } .slb_template_tag_ui { - @include opacity(0); + opacity: 0; } } @@ -143,19 +143,18 @@ //Small screen @media screen and (max-width: 480px) { - @mixin vsizing { + %vsizing { min-height: 100%; min-width: 320px; width: 100%; } - @include vsizing; + @extend %vsizing; .slb_viewer_layout { - @include vsizing; + @extend %vsizing; display: block; } .slb_container { - @include vsizing; - @include box-sizing-border; + @extend %vsizing; max-width: 100%; margin: 0; padding: 5px; diff --git a/themes/black/css/style.css b/themes/black/css/style.css index 4ddfa31..c2f46c7 100644 --- a/themes/black/css/style.css +++ b/themes/black/css/style.css @@ -1 +1 @@ -#slb_viewer_wrap .slb_theme_slb_black a,#slb_viewer_wrap .slb_theme_slb_black a:hover{color:#fff}#slb_viewer_wrap .slb_theme_slb_black .slb_loading{background-image:url("../images/loading.gif")}#slb_viewer_wrap .slb_theme_slb_black .slb_container{background-color:#151515}#slb_viewer_wrap .slb_theme_slb_black .slb_prev a{background-image:url("../images/nav_prev.png")}#slb_viewer_wrap .slb_theme_slb_black .slb_next a{background-image:url("../images/nav_next.png")}#slb_viewer_wrap .slb_theme_slb_black .slb_data_title{color:#e3e3e3}#slb_viewer_wrap .slb_theme_slb_black .slb_data_desc{color:#cecece}#slb_viewer_wrap .slb_theme_slb_black .slb_group_status{color:#999} +#slb_viewer_wrap .slb_theme_slb_black a ,#slb_viewer_wrap .slb_theme_slb_black a:hover {color:#ffffff;}#slb_viewer_wrap .slb_theme_slb_black .slb_loading {background-image:url('../images/loading.gif');}#slb_viewer_wrap .slb_theme_slb_black .slb_container {background-color:#151515;}#slb_viewer_wrap .slb_theme_slb_black .slb_prev a {background-image:url('../images/nav_prev.png');}#slb_viewer_wrap .slb_theme_slb_black .slb_next a {background-image:url('../images/nav_next.png');}#slb_viewer_wrap .slb_theme_slb_black .slb_data_title {color:#e3e3e3;}#slb_viewer_wrap .slb_theme_slb_black .slb_data_desc {color:#cecece;}#slb_viewer_wrap .slb_theme_slb_black .slb_group_status {color:#999999;} \ No newline at end of file diff --git a/themes/default/client.js b/themes/default/client.js index c80f075..f90f7a0 100644 --- a/themes/default/client.js +++ b/themes/default/client.js @@ -1,7 +1,10 @@ (function($) { $(document).ready(function() { -if ( typeof SLB == 'undefined' || typeof SLB.View == 'undefined' || typeof SLB.View.extend_theme == 'undefined' ) +//Validation +if ( typeof SLB === 'undefined' || typeof SLB.View === 'undefined' || typeof SLB.View.extend_theme !== 'function' ) { return false; +} +//Extend Theme SLB.View.extend_theme('slb_default', { /** * Define transition handlers @@ -14,7 +17,6 @@ SLB.View.extend_theme('slb_default', { * @return jQuery.Promise Resolved when transition is complete */ 'open': function(v, dfr) { - var t = this; var d = v.dom_get(), l = v.get_layout().hide(), o = v.get_overlay().hide(); @@ -58,13 +60,12 @@ SLB.View.extend_theme('slb_default', { var l = v.get_layout(), c = l.find('.slb_content'), spd = 'fast'; - var t = this; var reset = function() { //Reset state c.width('').height(''); l.css('opacity', ''); dfr.resolve(); - } + }; if ( v.animation_enabled() && document.documentElement.clientWidth > 480 ) { /* Standard */ var lanim = {opacity: 0, top: $(document).scrollTop() + ( $(window).height() / 2 )}, canim = {width: 0, height: 0}; @@ -92,7 +93,7 @@ SLB.View.extend_theme('slb_default', { 'load': function(v, dfr) { v.get_layout().find('.slb_loading').show(); if ( document.documentElement.clientWidth > 480 ) { - return v.get_layout().fadeIn().promise() + return v.get_layout().fadeIn().promise(); } else { v.get_layout().show(); dfr.resolve(); @@ -132,8 +133,7 @@ SLB.View.extend_theme('slb_default', { */ 'complete': function(v, dfr) { //Elements - var t = this, - l = v.get_layout(), + var l = v.get_layout(), loader = l.find('.slb_loading'), det = l.find('.slb_details'), det_data = det.find('.slb_data'), diff --git a/themes/default/css/style.css b/themes/default/css/style.css index 1cc9cec..a42c03a 100644 --- a/themes/default/css/style.css +++ b/themes/default/css/style.css @@ -1 +1 @@ -#slb_viewer_wrap .slb_theme_slb_default a,#slb_viewer_wrap .slb_theme_slb_default a:hover{border-bottom:none;color:#000;text-decoration:underline}#slb_viewer_wrap .slb_theme_slb_default .slb_viewer_layout{top:20px}#slb_viewer_wrap .slb_theme_slb_default .slb_container{-webkit-box-shadow:0 0 64px -40px #fcfcfc;-moz-box-shadow:0 0 64px -40px #fcfcfc;box-shadow:0 0 64px -40px #fcfcfc;border-radius:5px}#slb_viewer_wrap .slb_theme_slb_default .slb_loading{text-indent:-2000em}#slb_viewer_wrap .slb_theme_slb_default .slb_template_tag_ui{transition:opacity .5s}#slb_viewer_wrap .slb_theme_slb_default .slb_controls{position:absolute;top:8px;right:8px;width:75%;text-align:right}#slb_viewer_wrap .slb_theme_slb_default .slb_controls .slb_template_tag_ui{width:25px;height:25px;float:right;margin-left:2px;text-indent:-2000em;filter:progid:DXImageTransform.Microsoft.Alpha(Opacity=50);opacity:0.5}#slb_viewer_wrap .slb_theme_slb_default .slb_controls .slb_template_tag_ui:hover{filter:progid:DXImageTransform.Microsoft.Alpha(Opacity=80);opacity:0.8}#slb_viewer_wrap .slb_theme_slb_default .slb_controls .slb_slideshow .slb_template_tag{background:url("../images/ui_slideshow_play.png") 0 0 no-repeat}#slb_viewer_wrap .slb_theme_slb_default .slb_controls .slb_close .slb_template_tag{background:url("../images/ui_close.png") 0 0 no-repeat}#slb_viewer_wrap .slb_theme_slb_default.slideshow_active .slb_controls .slb_slideshow .slb_template_tag{background:url("../images/ui_slideshow_pause.png") 0 0 no-repeat}#slb_viewer_wrap .slb_theme_slb_default .slb_content .slb_prev .slb_template_tag,#slb_viewer_wrap .slb_theme_slb_default .slb_content .slb_next .slb_template_tag{position:absolute;top:20%;height:71%;width:45%;min-width:25px;min-height:33px;margin-left:4px;background-repeat:no-repeat;text-indent:-2000em;filter:progid:DXImageTransform.Microsoft.Alpha(Opacity=50);opacity:0.5}#slb_viewer_wrap .slb_theme_slb_default .slb_content{min-height:58px;min-width:50px}#slb_viewer_wrap .slb_theme_slb_default .slb_content .slb_prev .slb_template_tag{background-image:url("../images/nav_prev.png");background-position:left 45%}#slb_viewer_wrap .slb_theme_slb_default .slb_content .slb_next .slb_template_tag{right:4px;background-image:url("../images/nav_next.png");background-position:right 45%}#slb_viewer_wrap .slb_theme_slb_default .slb_content .slb_prev .slb_template_tag:hover,#slb_viewer_wrap .slb_theme_slb_default .slb_content .slb_next .slb_template_tag:hover{filter:progid:DXImageTransform.Microsoft.Alpha(Opacity=100);opacity:1}#slb_viewer_wrap .slb_theme_slb_default .slb_details{line-height:1.4em;overflow:hidden;position:relative}#slb_viewer_wrap .slb_theme_slb_default .slb_details .slb_data{caption-side:bottom}#slb_viewer_wrap .slb_theme_slb_default .slb_details .slb_nav{display:none}#slb_viewer_wrap .slb_theme_slb_default .slb_data_title,#slb_viewer_wrap .slb_theme_slb_default .slb_group_status{font-family:'Yanone Kaffeesatz', arial, sans-serif;font-size:23px;margin-right:.2em}#slb_viewer_wrap .slb_theme_slb_default .slb_group_status{color:#777;font-style:italic;font-size:18.4px}#slb_viewer_wrap .slb_theme_slb_default .slb_data_desc{display:block;margin-top:0.5em}@media screen and (max-width: 480px){#slb_viewer_wrap .slb_theme_slb_default .slb_container{-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none;border-radius:0}#slb_viewer_wrap .slb_theme_slb_default .slb_controls{top:3px;right:3px}#slb_viewer_wrap .slb_theme_slb_default .slb_content .slb_prev .slb_template_tag,#slb_viewer_wrap .slb_theme_slb_default .slb_content .slb_next .slb_template_tag{top:17%;height:79%}} +#slb_viewer_wrap .slb_theme_slb_default a ,#slb_viewer_wrap .slb_theme_slb_default a:hover {border-bottom:none;color:#000000;text-decoration:underline;}#slb_viewer_wrap .slb_theme_slb_default .slb_viewer_layout {top:20px;}#slb_viewer_wrap .slb_theme_slb_default .slb_container {box-shadow:0 0 64px -40px #fcfcfc;border-radius:5px;}#slb_viewer_wrap .slb_theme_slb_default .slb_loading {text-indent:-2000em;}#slb_viewer_wrap .slb_theme_slb_default .slb_template_tag_ui {transition:opacity 0.5s;}#slb_viewer_wrap .slb_theme_slb_default .slb_controls {position:absolute;top:8px;right:8px;width:75%;text-align:right;}#slb_viewer_wrap .slb_theme_slb_default .slb_controls .slb_template_tag_ui {width:25px;height:25px;float:right;margin-left:2px;text-indent:-2000em;opacity:0.5;}#slb_viewer_wrap .slb_theme_slb_default .slb_controls .slb_template_tag_ui:hover {opacity:0.8;}#slb_viewer_wrap .slb_theme_slb_default .slb_controls .slb_slideshow .slb_template_tag {background:url('../images/ui_slideshow_play.png') 0 0 no-repeat;}#slb_viewer_wrap .slb_theme_slb_default .slb_controls .slb_close .slb_template_tag {background:url('../images/ui_close.png') 0 0 no-repeat;}#slb_viewer_wrap .slb_theme_slb_default.slideshow_active .slb_controls .slb_slideshow .slb_template_tag {background:url('../images/ui_slideshow_pause.png') 0 0 no-repeat;}#slb_viewer_wrap .slb_theme_slb_default .slb_content .slb_prev .slb_template_tag ,#slb_viewer_wrap .slb_theme_slb_default .slb_content .slb_next .slb_template_tag {position:absolute;top:20%;height:71%;width:45%;min-width:25px;min-height:33px;margin-left:4px;background-repeat:no-repeat;text-indent:-2000em;opacity:0.5;}#slb_viewer_wrap .slb_theme_slb_default .slb_content {min-height:58px;min-width:50px;}#slb_viewer_wrap .slb_theme_slb_default .slb_content .slb_prev .slb_template_tag {background-image:url('../images/nav_prev.png');background-position:left 45%;}#slb_viewer_wrap .slb_theme_slb_default .slb_content .slb_next .slb_template_tag {right:4px;background-image:url('../images/nav_next.png');background-position:right 45%;}#slb_viewer_wrap .slb_theme_slb_default .slb_content .slb_prev .slb_template_tag:hover ,#slb_viewer_wrap .slb_theme_slb_default .slb_content .slb_next .slb_template_tag:hover {opacity:1;}#slb_viewer_wrap .slb_theme_slb_default .slb_details {line-height:1.4em;overflow:hidden;position:relative;}#slb_viewer_wrap .slb_theme_slb_default .slb_details .slb_data {caption-side:bottom;}#slb_viewer_wrap .slb_theme_slb_default .slb_details .slb_nav {display:none;}#slb_viewer_wrap .slb_theme_slb_default .slb_data_title ,#slb_viewer_wrap .slb_theme_slb_default .slb_group_status {font-family:'Yanone Kaffeesatz',arial,sans-serif;font-size:23px;margin-right:0.2em;}#slb_viewer_wrap .slb_theme_slb_default .slb_group_status {color:#777777;font-style:italic;font-size:18.4px;}#slb_viewer_wrap .slb_theme_slb_default .slb_data_desc {display:block;margin-top:0.5em;}@media screen and (max-width: 480px){#slb_viewer_wrap .slb_theme_slb_default .slb_container {box-shadow:none;border-radius:0;}#slb_viewer_wrap .slb_theme_slb_default .slb_controls {top:3px;right:3px;}#slb_viewer_wrap .slb_theme_slb_default .slb_content .slb_prev .slb_template_tag ,#slb_viewer_wrap .slb_theme_slb_default .slb_content .slb_next .slb_template_tag {top:17%;height:79%;}} \ No newline at end of file diff --git a/themes/default/sass/style.scss b/themes/default/sass/style.scss index 6babc4a..a0dc1c1 100644 --- a/themes/default/sass/style.scss +++ b/themes/default/sass/style.scss @@ -1,7 +1,3 @@ -//Imports - -@import "compass"; - //Variables $nav_width: 25px; @@ -9,15 +5,9 @@ $nav_height: 33px; $ui_controls_width: 25px; $ui_controls_height: 25px; -//Mixins - -@mixin box-sizing-border { - @include box-sizing(border-box); -} - #slb_viewer_wrap { .slb_theme_slb_default { - /* General */ + //General a, a:hover { border-bottom:none; @@ -30,7 +20,7 @@ $ui_controls_height: 25px; } .slb_container { - @include box-shadow(0 0 64px -40px #fcfcfc); + box-shadow: 0 0 64px -40px #fcfcfc; border-radius: 5px; } @@ -42,7 +32,7 @@ $ui_controls_height: 25px; transition: opacity .5s; } - /* UI */ + //UI .slb_controls { position: absolute; top: 8px; @@ -56,11 +46,11 @@ $ui_controls_height: 25px; float: right; margin-left: 2px; text-indent: -2000em; - @include opacity(.5); + opacity: 0.5; } .slb_template_tag_ui:hover { - @include opacity(.8); + opacity: 0.8; } .slb_slideshow .slb_template_tag { @@ -76,7 +66,7 @@ $ui_controls_height: 25px; background: url('../images/ui_slideshow_pause.png') 0 0 no-repeat; } - /* Navigation */ + //Navigation $ui_nav_pos: 45%; %ui_nav { position: absolute; @@ -88,10 +78,10 @@ $ui_controls_height: 25px; margin-left: 4px; background-repeat: no-repeat; text-indent: -2000em; - @include opacity(.5); + opacity: 0.5; } - /* Content */ + //Content .slb_content { min-height: $nav_height + $ui_controls_height; min-width: $nav_width * 2; @@ -110,7 +100,9 @@ $ui_controls_height: 25px; .slb_prev, .slb_next { .slb_template_tag { @extend %ui_nav; - &:hover { @include opacity(1); } + &:hover { + opacity: 1 + } } } } @@ -129,17 +121,16 @@ $ui_controls_height: 25px; } } - /* Title */ + //Title $title-size: 23px; - .slb_data_title { + .slb_data_title, .slb_group_status { font-family: 'Yanone Kaffeesatz', arial, sans-serif; font-size: $title-size; margin-right: .2em; } .slb_group_status { - @extend .slb_data_title; color: #777; font-style: italic; font-size: $title-size * .8; @@ -155,7 +146,7 @@ $ui_controls_height: 25px; //Small screen @media screen and (max-width: 480px) { .slb_container { - @include box-shadow(none); + box-shadow: none; border-radius: 0; } .slb_controls {