diff --git a/.bzrignore b/.bzrignore index e43b0f98..a3c39f02 100644 --- a/.bzrignore +++ b/.bzrignore @@ -1 +1,2 @@ .DS_Store +build/js diff --git a/build/js/jquery-1.3.2.js b/build/js/jquery-1.3.2.js deleted file mode 100644 index 462cde56..00000000 --- a/build/js/jquery-1.3.2.js +++ /dev/null @@ -1,4376 +0,0 @@ -/*! - * jQuery JavaScript Library v1.3.2 - * http://jquery.com/ - * - * Copyright (c) 2009 John Resig - * Dual licensed under the MIT and GPL licenses. - * http://docs.jquery.com/License - * - * Date: 2009-02-19 17:34:21 -0500 (Thu, 19 Feb 2009) - * Revision: 6246 - */ -(function(){ - -var - // Will speed up references to window, and allows munging its name. - window = this, - // Will speed up references to undefined, and allows munging its name. - undefined, - // Map over jQuery in case of overwrite - _jQuery = window.jQuery, - // Map over the $ in case of overwrite - _$ = window.$, - - jQuery = window.jQuery = window.$ = function( selector, context ) { - // The jQuery object is actually just the init constructor 'enhanced' - return new jQuery.fn.init( selector, context ); - }, - - // A simple way to check for HTML strings or ID strings - // (both of which we optimize for) - quickExpr = /^[^<]*(<(.|\s)+>)[^>]*$|^#([\w-]+)$/, - // Is it a simple selector - isSimple = /^.[^:#\[\.,]*$/; - -jQuery.fn = jQuery.prototype = { - init: function( selector, context ) { - // Make sure that a selection was provided - selector = selector || document; - - // Handle $(DOMElement) - if ( selector.nodeType ) { - this[0] = selector; - this.length = 1; - this.context = selector; - return this; - } - // Handle HTML strings - if ( typeof selector === "string" ) { - // Are we dealing with HTML string or an ID? - var match = quickExpr.exec( selector ); - - // Verify a match, and that no context was specified for #id - if ( match && (match[1] || !context) ) { - - // HANDLE: $(html) -> $(array) - if ( match[1] ) - selector = jQuery.clean( [ match[1] ], context ); - - // HANDLE: $("#id") - else { - var elem = document.getElementById( match[3] ); - - // Handle the case where IE and Opera return items - // by name instead of ID - if ( elem && elem.id != match[3] ) - return jQuery().find( selector ); - - // Otherwise, we inject the element directly into the jQuery object - var ret = jQuery( elem || [] ); - ret.context = document; - ret.selector = selector; - return ret; - } - - // HANDLE: $(expr, [context]) - // (which is just equivalent to: $(content).find(expr) - } else - return jQuery( context ).find( selector ); - - // HANDLE: $(function) - // Shortcut for document ready - } else if ( jQuery.isFunction( selector ) ) - return jQuery( document ).ready( selector ); - - // Make sure that old selector state is passed along - if ( selector.selector && selector.context ) { - this.selector = selector.selector; - this.context = selector.context; - } - - return this.setArray(jQuery.isArray( selector ) ? - selector : - jQuery.makeArray(selector)); - }, - - // Start with an empty selector - selector: "", - - // The current version of jQuery being used - jquery: "1.3.2", - - // The number of elements contained in the matched element set - size: function() { - return this.length; - }, - - // Get the Nth element in the matched element set OR - // Get the whole matched element set as a clean array - get: function( num ) { - return num === undefined ? - - // Return a 'clean' array - Array.prototype.slice.call( this ) : - - // Return just the object - this[ num ]; - }, - - // Take an array of elements and push it onto the stack - // (returning the new matched element set) - pushStack: function( elems, name, selector ) { - // Build a new jQuery matched element set - var ret = jQuery( elems ); - - // Add the old object onto the stack (as a reference) - ret.prevObject = this; - - ret.context = this.context; - - if ( name === "find" ) - ret.selector = this.selector + (this.selector ? " " : "") + selector; - else if ( name ) - ret.selector = this.selector + "." + name + "(" + selector + ")"; - - // Return the newly-formed element set - return ret; - }, - - // Force the current matched set of elements to become - // the specified array of elements (destroying the stack in the process) - // You should use pushStack() in order to do this, but maintain the stack - setArray: function( elems ) { - // Resetting the length to 0, then using the native Array push - // is a super-fast way to populate an object with array-like properties - this.length = 0; - Array.prototype.push.apply( this, elems ); - - return this; - }, - - // Execute a callback for every element in the matched set. - // (You can seed the arguments with an array of args, but this is - // only used internally.) - each: function( callback, args ) { - return jQuery.each( this, callback, args ); - }, - - // Determine the position of an element within - // the matched set of elements - index: function( elem ) { - // Locate the position of the desired element - return jQuery.inArray( - // If it receives a jQuery object, the first element is used - elem && elem.jquery ? elem[0] : elem - , this ); - }, - - attr: function( name, value, type ) { - var options = name; - - // Look for the case where we're accessing a style value - if ( typeof name === "string" ) - if ( value === undefined ) - return this[0] && jQuery[ type || "attr" ]( this[0], name ); - - else { - options = {}; - options[ name ] = value; - } - - // Check to see if we're setting style values - return this.each(function(i){ - // Set all the styles - for ( name in options ) - jQuery.attr( - type ? - this.style : - this, - name, jQuery.prop( this, options[ name ], type, i, name ) - ); - }); - }, - - css: function( key, value ) { - // ignore negative width and height values - if ( (key == 'width' || key == 'height') && parseFloat(value) < 0 ) - value = undefined; - return this.attr( key, value, "curCSS" ); - }, - - text: function( text ) { - if ( typeof text !== "object" && text != null ) - return this.empty().append( (this[0] && this[0].ownerDocument || document).createTextNode( text ) ); - - var ret = ""; - - jQuery.each( text || this, function(){ - jQuery.each( this.childNodes, function(){ - if ( this.nodeType != 8 ) - ret += this.nodeType != 1 ? - this.nodeValue : - jQuery.fn.text( [ this ] ); - }); - }); - - return ret; - }, - - wrapAll: function( html ) { - if ( this[0] ) { - // The elements to wrap the target around - var wrap = jQuery( html, this[0].ownerDocument ).clone(); - - if ( this[0].parentNode ) - wrap.insertBefore( this[0] ); - - wrap.map(function(){ - var elem = this; - - while ( elem.firstChild ) - elem = elem.firstChild; - - return elem; - }).append(this); - } - - return this; - }, - - wrapInner: function( html ) { - return this.each(function(){ - jQuery( this ).contents().wrapAll( html ); - }); - }, - - wrap: function( html ) { - return this.each(function(){ - jQuery( this ).wrapAll( html ); - }); - }, - - append: function() { - return this.domManip(arguments, true, function(elem){ - if (this.nodeType == 1) - this.appendChild( elem ); - }); - }, - - prepend: function() { - return this.domManip(arguments, true, function(elem){ - if (this.nodeType == 1) - this.insertBefore( elem, this.firstChild ); - }); - }, - - before: function() { - return this.domManip(arguments, false, function(elem){ - this.parentNode.insertBefore( elem, this ); - }); - }, - - after: function() { - return this.domManip(arguments, false, function(elem){ - this.parentNode.insertBefore( elem, this.nextSibling ); - }); - }, - - end: function() { - return this.prevObject || jQuery( [] ); - }, - - // For internal use only. - // Behaves like an Array's method, not like a jQuery method. - push: [].push, - sort: [].sort, - splice: [].splice, - - find: function( selector ) { - if ( this.length === 1 ) { - var ret = this.pushStack( [], "find", selector ); - ret.length = 0; - jQuery.find( selector, this[0], ret ); - return ret; - } else { - return this.pushStack( jQuery.unique(jQuery.map(this, function(elem){ - return jQuery.find( selector, elem ); - })), "find", selector ); - } - }, - - clone: function( events ) { - // Do the clone - var ret = this.map(function(){ - if ( !jQuery.support.noCloneEvent && !jQuery.isXMLDoc(this) ) { - // IE copies events bound via attachEvent when - // using cloneNode. Calling detachEvent on the - // clone will also remove the events from the orignal - // In order to get around this, we use innerHTML. - // Unfortunately, this means some modifications to - // attributes in IE that are actually only stored - // as properties will not be copied (such as the - // the name attribute on an input). - var html = this.outerHTML; - if ( !html ) { - var div = this.ownerDocument.createElement("div"); - div.appendChild( this.cloneNode(true) ); - html = div.innerHTML; - } - - return jQuery.clean([html.replace(/ jQuery\d+="(?:\d+|null)"/g, "").replace(/^\s*/, "")])[0]; - } else - return this.cloneNode(true); - }); - - // Copy the events from the original to the clone - if ( events === true ) { - var orig = this.find("*").andSelf(), i = 0; - - ret.find("*").andSelf().each(function(){ - if ( this.nodeName !== orig[i].nodeName ) - return; - - var events = jQuery.data( orig[i], "events" ); - - for ( var type in events ) { - for ( var handler in events[ type ] ) { - jQuery.event.add( this, type, events[ type ][ handler ], events[ type ][ handler ].data ); - } - } - - i++; - }); - } - - // Return the cloned set - return ret; - }, - - filter: function( selector ) { - return this.pushStack( - jQuery.isFunction( selector ) && - jQuery.grep(this, function(elem, i){ - return selector.call( elem, i ); - }) || - - jQuery.multiFilter( selector, jQuery.grep(this, function(elem){ - return elem.nodeType === 1; - }) ), "filter", selector ); - }, - - closest: function( selector ) { - var pos = jQuery.expr.match.POS.test( selector ) ? jQuery(selector) : null, - closer = 0; - - return this.map(function(){ - var cur = this; - while ( cur && cur.ownerDocument ) { - if ( pos ? pos.index(cur) > -1 : jQuery(cur).is(selector) ) { - jQuery.data(cur, "closest", closer); - return cur; - } - cur = cur.parentNode; - closer++; - } - }); - }, - - not: function( selector ) { - if ( typeof selector === "string" ) - // test special case where just one selector is passed in - if ( isSimple.test( selector ) ) - return this.pushStack( jQuery.multiFilter( selector, this, true ), "not", selector ); - else - selector = jQuery.multiFilter( selector, this ); - - var isArrayLike = selector.length && selector[selector.length - 1] !== undefined && !selector.nodeType; - return this.filter(function() { - return isArrayLike ? jQuery.inArray( this, selector ) < 0 : this != selector; - }); - }, - - add: function( selector ) { - return this.pushStack( jQuery.unique( jQuery.merge( - this.get(), - typeof selector === "string" ? - jQuery( selector ) : - jQuery.makeArray( selector ) - ))); - }, - - is: function( selector ) { - return !!selector && jQuery.multiFilter( selector, this ).length > 0; - }, - - hasClass: function( selector ) { - return !!selector && this.is( "." + selector ); - }, - - val: function( value ) { - if ( value === undefined ) { - var elem = this[0]; - - if ( elem ) { - if( jQuery.nodeName( elem, 'option' ) ) - return (elem.attributes.value || {}).specified ? elem.value : elem.text; - - // We need to handle select boxes special - if ( jQuery.nodeName( elem, "select" ) ) { - var index = elem.selectedIndex, - values = [], - options = elem.options, - one = elem.type == "select-one"; - - // Nothing was selected - if ( index < 0 ) - return null; - - // Loop through all the selected options - for ( var i = one ? index : 0, max = one ? index + 1 : options.length; i < max; i++ ) { - var option = options[ i ]; - - if ( option.selected ) { - // Get the specifc value for the option - value = jQuery(option).val(); - - // We don't need an array for one selects - if ( one ) - return value; - - // Multi-Selects return an array - values.push( value ); - } - } - - return values; - } - - // Everything else, we just grab the value - return (elem.value || "").replace(/\r/g, ""); - - } - - return undefined; - } - - if ( typeof value === "number" ) - value += ''; - - return this.each(function(){ - if ( this.nodeType != 1 ) - return; - - if ( jQuery.isArray(value) && /radio|checkbox/.test( this.type ) ) - this.checked = (jQuery.inArray(this.value, value) >= 0 || - jQuery.inArray(this.name, value) >= 0); - - else if ( jQuery.nodeName( this, "select" ) ) { - var values = jQuery.makeArray(value); - - jQuery( "option", this ).each(function(){ - this.selected = (jQuery.inArray( this.value, values ) >= 0 || - jQuery.inArray( this.text, values ) >= 0); - }); - - if ( !values.length ) - this.selectedIndex = -1; - - } else - this.value = value; - }); - }, - - html: function( value ) { - return value === undefined ? - (this[0] ? - this[0].innerHTML.replace(/ jQuery\d+="(?:\d+|null)"/g, "") : - null) : - this.empty().append( value ); - }, - - replaceWith: function( value ) { - return this.after( value ).remove(); - }, - - eq: function( i ) { - return this.slice( i, +i + 1 ); - }, - - slice: function() { - return this.pushStack( Array.prototype.slice.apply( this, arguments ), - "slice", Array.prototype.slice.call(arguments).join(",") ); - }, - - map: function( callback ) { - return this.pushStack( jQuery.map(this, function(elem, i){ - return callback.call( elem, i, elem ); - })); - }, - - andSelf: function() { - return this.add( this.prevObject ); - }, - - domManip: function( args, table, callback ) { - if ( this[0] ) { - var fragment = (this[0].ownerDocument || this[0]).createDocumentFragment(), - scripts = jQuery.clean( args, (this[0].ownerDocument || this[0]), fragment ), - first = fragment.firstChild; - - if ( first ) - for ( var i = 0, l = this.length; i < l; i++ ) - callback.call( root(this[i], first), this.length > 1 || i > 0 ? - fragment.cloneNode(true) : fragment ); - - if ( scripts ) - jQuery.each( scripts, evalScript ); - } - - return this; - - function root( elem, cur ) { - return table && jQuery.nodeName(elem, "table") && jQuery.nodeName(cur, "tr") ? - (elem.getElementsByTagName("tbody")[0] || - elem.appendChild(elem.ownerDocument.createElement("tbody"))) : - elem; - } - } -}; - -// Give the init function the jQuery prototype for later instantiation -jQuery.fn.init.prototype = jQuery.fn; - -function evalScript( i, elem ) { - if ( elem.src ) - jQuery.ajax({ - url: elem.src, - async: false, - dataType: "script" - }); - - else - jQuery.globalEval( elem.text || elem.textContent || elem.innerHTML || "" ); - - if ( elem.parentNode ) - elem.parentNode.removeChild( elem ); -} - -function now(){ - return +new Date; -} - -jQuery.extend = jQuery.fn.extend = function() { - // copy reference to target object - var target = arguments[0] || {}, i = 1, length = arguments.length, deep = false, options; - - // Handle a deep copy situation - if ( typeof target === "boolean" ) { - deep = target; - target = arguments[1] || {}; - // skip the boolean and the target - i = 2; - } - - // Handle case when target is a string or something (possible in deep copy) - if ( typeof target !== "object" && !jQuery.isFunction(target) ) - target = {}; - - // extend jQuery itself if only one argument is passed - if ( length == i ) { - target = this; - --i; - } - - for ( ; i < length; i++ ) - // Only deal with non-null/undefined values - if ( (options = arguments[ i ]) != null ) - // Extend the base object - for ( var name in options ) { - var src = target[ name ], copy = options[ name ]; - - // Prevent never-ending loop - if ( target === copy ) - continue; - - // Recurse if we're merging object values - if ( deep && copy && typeof copy === "object" && !copy.nodeType ) - target[ name ] = jQuery.extend( deep, - // Never move original objects, clone them - src || ( copy.length != null ? [ ] : { } ) - , copy ); - - // Don't bring in undefined values - else if ( copy !== undefined ) - target[ name ] = copy; - - } - - // Return the modified object - return target; -}; - -// exclude the following css properties to add px -var exclude = /z-?index|font-?weight|opacity|zoom|line-?height/i, - // cache defaultView - defaultView = document.defaultView || {}, - toString = Object.prototype.toString; - -jQuery.extend({ - noConflict: function( deep ) { - window.$ = _$; - - if ( deep ) - window.jQuery = _jQuery; - - return jQuery; - }, - - // See test/unit/core.js for details concerning isFunction. - // Since version 1.3, DOM methods and functions like alert - // aren't supported. They return false on IE (#2968). - isFunction: function( obj ) { - return toString.call(obj) === "[object Function]"; - }, - - isArray: function( obj ) { - return toString.call(obj) === "[object Array]"; - }, - - // check if an element is in a (or is an) XML document - isXMLDoc: function( elem ) { - return elem.nodeType === 9 && elem.documentElement.nodeName !== "HTML" || - !!elem.ownerDocument && jQuery.isXMLDoc( elem.ownerDocument ); - }, - - // Evalulates a script in a global context - globalEval: function( data ) { - if ( data && /\S/.test(data) ) { - // Inspired by code by Andrea Giammarchi - // http://webreflection.blogspot.com/2007/08/global-scope-evaluation-and-dom.html - var head = document.getElementsByTagName("head")[0] || document.documentElement, - script = document.createElement("script"); - - script.type = "text/javascript"; - if ( jQuery.support.scriptEval ) - script.appendChild( document.createTextNode( data ) ); - else - script.text = data; - - // Use insertBefore instead of appendChild to circumvent an IE6 bug. - // This arises when a base node is used (#2709). - head.insertBefore( script, head.firstChild ); - head.removeChild( script ); - } - }, - - nodeName: function( elem, name ) { - return elem.nodeName && elem.nodeName.toUpperCase() == name.toUpperCase(); - }, - - // args is for internal usage only - each: function( object, callback, args ) { - var name, i = 0, length = object.length; - - if ( args ) { - if ( length === undefined ) { - for ( name in object ) - if ( callback.apply( object[ name ], args ) === false ) - break; - } else - for ( ; i < length; ) - if ( callback.apply( object[ i++ ], args ) === false ) - break; - - // A special, fast, case for the most common use of each - } else { - if ( length === undefined ) { - for ( name in object ) - if ( callback.call( object[ name ], name, object[ name ] ) === false ) - break; - } else - for ( var value = object[0]; - i < length && callback.call( value, i, value ) !== false; value = object[++i] ){} - } - - return object; - }, - - prop: function( elem, value, type, i, name ) { - // Handle executable functions - if ( jQuery.isFunction( value ) ) - value = value.call( elem, i ); - - // Handle passing in a number to a CSS property - return typeof value === "number" && type == "curCSS" && !exclude.test( name ) ? - value + "px" : - value; - }, - - className: { - // internal only, use addClass("class") - add: function( elem, classNames ) { - jQuery.each((classNames || "").split(/\s+/), function(i, className){ - if ( elem.nodeType == 1 && !jQuery.className.has( elem.className, className ) ) - elem.className += (elem.className ? " " : "") + className; - }); - }, - - // internal only, use removeClass("class") - remove: function( elem, classNames ) { - if (elem.nodeType == 1) - elem.className = classNames !== undefined ? - jQuery.grep(elem.className.split(/\s+/), function(className){ - return !jQuery.className.has( classNames, className ); - }).join(" ") : - ""; - }, - - // internal only, use hasClass("class") - has: function( elem, className ) { - return elem && jQuery.inArray( className, (elem.className || elem).toString().split(/\s+/) ) > -1; - } - }, - - // A method for quickly swapping in/out CSS properties to get correct calculations - swap: function( elem, options, callback ) { - var old = {}; - // Remember the old values, and insert the new ones - for ( var name in options ) { - old[ name ] = elem.style[ name ]; - elem.style[ name ] = options[ name ]; - } - - callback.call( elem ); - - // Revert the old values - for ( var name in options ) - elem.style[ name ] = old[ name ]; - }, - - css: function( elem, name, force, extra ) { - if ( name == "width" || name == "height" ) { - var val, props = { position: "absolute", visibility: "hidden", display:"block" }, which = name == "width" ? [ "Left", "Right" ] : [ "Top", "Bottom" ]; - - function getWH() { - val = name == "width" ? elem.offsetWidth : elem.offsetHeight; - - if ( extra === "border" ) - return; - - jQuery.each( which, function() { - if ( !extra ) - val -= parseFloat(jQuery.curCSS( elem, "padding" + this, true)) || 0; - if ( extra === "margin" ) - val += parseFloat(jQuery.curCSS( elem, "margin" + this, true)) || 0; - else - val -= parseFloat(jQuery.curCSS( elem, "border" + this + "Width", true)) || 0; - }); - } - - if ( elem.offsetWidth !== 0 ) - getWH(); - else - jQuery.swap( elem, props, getWH ); - - return Math.max(0, Math.round(val)); - } - - return jQuery.curCSS( elem, name, force ); - }, - - curCSS: function( elem, name, force ) { - var ret, style = elem.style; - - // We need to handle opacity special in IE - if ( name == "opacity" && !jQuery.support.opacity ) { - ret = jQuery.attr( style, "opacity" ); - - return ret == "" ? - "1" : - ret; - } - - // Make sure we're using the right name for getting the float value - if ( name.match( /float/i ) ) - name = styleFloat; - - if ( !force && style && style[ name ] ) - ret = style[ name ]; - - else if ( defaultView.getComputedStyle ) { - - // Only "float" is needed here - if ( name.match( /float/i ) ) - name = "float"; - - name = name.replace( /([A-Z])/g, "-$1" ).toLowerCase(); - - var computedStyle = defaultView.getComputedStyle( elem, null ); - - if ( computedStyle ) - ret = computedStyle.getPropertyValue( name ); - - // We should always get a number back from opacity - if ( name == "opacity" && ret == "" ) - ret = "1"; - - } else if ( elem.currentStyle ) { - var camelCase = name.replace(/\-(\w)/g, function(all, letter){ - return letter.toUpperCase(); - }); - - ret = elem.currentStyle[ name ] || elem.currentStyle[ camelCase ]; - - // From the awesome hack by Dean Edwards - // http://erik.eae.net/archives/2007/07/27/18.54.15/#comment-102291 - - // If we're not dealing with a regular pixel number - // but a number that has a weird ending, we need to convert it to pixels - if ( !/^\d+(px)?$/i.test( ret ) && /^\d/.test( ret ) ) { - // Remember the original values - var left = style.left, rsLeft = elem.runtimeStyle.left; - - // Put in the new values to get a computed value out - elem.runtimeStyle.left = elem.currentStyle.left; - style.left = ret || 0; - ret = style.pixelLeft + "px"; - - // Revert the changed values - style.left = left; - elem.runtimeStyle.left = rsLeft; - } - } - - return ret; - }, - - clean: function( elems, context, fragment ) { - context = context || document; - - // !context.createElement fails in IE with an error but returns typeof 'object' - if ( typeof context.createElement === "undefined" ) - context = context.ownerDocument || context[0] && context[0].ownerDocument || document; - - // If a single string is passed in and it's a single tag - // just do a createElement and skip the rest - if ( !fragment && elems.length === 1 && typeof elems[0] === "string" ) { - var match = /^<(\w+)\s*\/?>$/.exec(elems[0]); - if ( match ) - return [ context.createElement( match[1] ) ]; - } - - var ret = [], scripts = [], div = context.createElement("div"); - - jQuery.each(elems, function(i, elem){ - if ( typeof elem === "number" ) - elem += ''; - - if ( !elem ) - return; - - // Convert html string into DOM nodes - if ( typeof elem === "string" ) { - // Fix "XHTML"-style tags in all browsers - elem = elem.replace(/(<(\w+)[^>]*?)\/>/g, function(all, front, tag){ - return tag.match(/^(abbr|br|col|img|input|link|meta|param|hr|area|embed)$/i) ? - all : - front + ">"; - }); - - // Trim whitespace, otherwise indexOf won't work as expected - var tags = elem.replace(/^\s+/, "").substring(0, 10).toLowerCase(); - - var wrap = - // option or optgroup - !tags.indexOf("", "" ] || - - !tags.indexOf("", "" ] || - - tags.match(/^<(thead|tbody|tfoot|colg|cap)/) && - [ 1, "", "
" ] || - - !tags.indexOf("", "" ] || - - // matched above - (!tags.indexOf("", "" ] || - - !tags.indexOf("", "" ] || - - // IE can't serialize and - - - + diff --git a/demos/calendar/js/calendar.js b/demos/calendar/js/calendar.js index fea6854b..906c7c50 100644 --- a/demos/calendar/js/calendar.js +++ b/demos/calendar/js/calendar.js @@ -1,133 +1,139 @@ -$(function() { +Ox.UI(function() { - Ox.theme('modern'); + Ox.UI.ready(function() { - new Ox.Calendar({ - date: new Date(0), - dates: [ - {name: 'Genghis Khan', start: new Date('1162'), stop: new Date('1228'), type: 'Person'}, - {name: 'Marco Polo', start: new Date('1254'), stop: new Date('1324-01-09'), type: 'Person'}, - {name: 'Columbus', start: new Date('1451-08-22'), stop: new Date('1506-05-21'), type: 'Person'}, - {name: 'Da Vinci', start: new Date('1452-04-15'), stop: new Date('1519-05-03'), type: 'Person'}, - {name: 'Michelangelo', start: new Date('1475-03-06'), stop: new Date('1564-02-19'), type: 'Person'}, - {name: 'Bruegel', start: new Date('1525'), stop: new Date('1569-09-10'), type: 'Person'}, - {name: 'Galilei', start: new Date('1564-02-15'), stop: new Date('1642-01-09'), type: 'Person'}, - {name: 'Shakespeare', start: new Date('1564-04-26'), stop: new Date('1616-04-24'), type: 'Person'}, - {name: 'Descartes', start: new Date('1596-03-31'), stop: new Date('1650-02-12'), type: 'Person'}, - {name: 'Velazquez', start: new Date('1599-06-06'), stop: new Date('1660-08-07'), type: 'Person'}, - {name: 'Vermeer', start: new Date('1632-10-31'), stop: new Date('1675-12-16'), type: 'Person'}, - {name: 'Spinoza', start: new Date('1632-11-24'), stop: new Date('1677-02-22'), type: 'Person'}, - {name: 'Louix XIV', start: new Date('1638-09-05'), stop: new Date('1715-09-02'), type: 'Person'}, - {name: 'Newton', start: new Date('1643-01-04'), stop: new Date('1727-04-01'), type: 'Person'}, - {name: 'Leibniz', start: new Date('1646-07-01'), stop: new Date('1716-11-15'), type: 'Person'}, - {name: 'Kant', start: new Date('1724-04-22'), stop: new Date('1804-02-13'), type: 'Person'}, - {name: 'Napoleon', start: new Date('1769-08-15'), stop: new Date('1821-05-06'), type: 'Person'}, - {name: 'Hegel', start: new Date('1770-08-27'), stop: new Date('1831-11-15'), type: 'Person'}, - {name: 'Darwin', start: new Date('1809-02-12'), stop: new Date('1882-04-20'), type: 'Person'}, - {name: 'Marx', start: new Date('1818-05-05'), stop: new Date('1883-03-15'), type: 'Person'}, - /* - {name: 'Gandhi', start: new Date('1869-10-02'), stop: new Date('1948-01-31'), type: 'Person'}, - {name: 'Lenin', start: new Date('1870-04-22'), stop: new Date('1924-01-22'), type: 'Person'}, - {name: 'Stalin', start: new Date('1878-12-18'), stop: new Date('1953-03-06'), type: 'Person'}, - {name: 'Einstein', start: new Date('1879-03-14'), stop: new Date('1955-04-19'), type: 'Person'}, - {name: 'Picasso', start: new Date('1881-10-25'), stop: new Date('1973-04-09'), type: 'Person'}, - {name: 'Roosevelt', start: new Date('1882-01-30'), stop: new Date('1945-04-13'), type: 'Person'}, - {name: 'Hitler', start: new Date('1889-04-20'), stop: new Date('1945-05-01'), type: 'Person'}, - {name: 'Hitchcock', start: new Date('1899-08-13'), stop: new Date('1980-04-30'), type: 'Person'}, - {name: 'Turing', start: new Date('1912-06-23'), stop: new Date('1954-06-08'), type: 'Person'}, - {name: 'Kennedy', start: new Date('1917-05-29'), stop: new Date('1963-11-23'), type: 'Person'}, - {name: 'Deleuze', start: new Date('1925-01-18'), stop: new Date('1995-11-05'), type: 'Person'}, - {name: 'Warhol', start: new Date('1928-08-06'), stop: new Date('1987-02-23'), type: 'Person'}, - {name: 'Debord', start: new Date('1931-12-28'), stop: new Date('1994-12-01'), type: 'Person'}, - */ - {name: 'Test', start: new Date('1970-01-01'), stop: new Date('1970-01-02')}, - {name: 'Battle of Hastings', start: new Date('1066-10-14'), stop: new Date('1066-10-15')}, - {name: 'Renaissance', start: new Date('1300'), stop: new Date('1700')}, - {name: 'Spanish Inquisition', start: new Date('1480-09-27'), stop: new Date('1820-03-10')}, - {name: 'Discovery of America', start: new Date('1492'), stop: new Date('1493')}, - {name: 'East India Company', start: new Date('1600'), stop: new Date('1874')}, - {name: 'Thirty Years\' War', start: new Date('1618'), stop: new Date('1649')}, - {name: 'Declaration of Independence', start: new Date('1776-07-04'), stop: new Date('1776-07-05')}, - {name: 'French Revolution', start: new Date('1789'), stop: new Date('1800')}, - {name: 'Storming of the Bastille', start: new Date('1789-07-14'), stop: new Date('1789-07-15')}, - {name: 'Napoleonic Era', start: new Date('1799-11-09'), stop: new Date('1815-06-29')}, - {name: 'The Year Without a Summer', start: new Date('1816'), stop: new Date('1817')}, - {name: 'Queen Victoria', start: new Date('1837-06-20'), stop: new Date('1901-01-23')}, - {name: 'Crimean War', start: new Date('1853-10'), stop: new Date('1856-03')}, - {name: 'American Civil War', start: new Date('1861-04-12'), stop: new Date('1865-04-10')}, - {name: 'Franco-Prussian War', start: new Date('1870-07-19'), stop: new Date('1871-05-11')}, - {name: 'Paris Commune', start: new Date('1871-03-18'), stop: new Date('1871-05-29')}, - {name: 'Congo Free State', start: new Date('1885-07-11'), stop: new Date('1908-11-16')}, - {name: '20th century', start: new Date('1900'), stop: new Date('2000')}, - {name: 'Titanic', start: new Date('1912-04-15'), stop: new Date('1912-04-16')}, - {name: 'World War One', start: new Date('1914-07-28'), stop: new Date('1918-11-12')}, - {name: 'Battle of Verdun', start: new Date('1916-02-21'), stop: new Date('1916-12-19')}, - {name: 'Russian Revolution', start: new Date('1917'), stop: new Date('1918')}, - {name: 'October Revolution', start: new Date('1917-11-07'), stop: new Date('1917-11-09')}, - {name: 'Spanish Flu', start: new Date('1918'), stop: new Date('1919')}, - {name: 'Weimar Republic', start: new Date('1918-11-09'), stop: new Date('1933-01-30')}, - {name: 'Treaty of Versailles', start: new Date('1919-06-28'), stop: new Date('1919-06-29')}, - {name: '1920s', start: new Date('1920'), stop: new Date('1930')}, - {name: 'Soviet Union', start: new Date('1922-12-30'), stop: new Date('1991-12-27')}, - {name: 'The Third Reich', start: new Date('1933-01-30'), stop: new Date('1945-05-09')}, - {name: 'Spanish Civil War', start: new Date('1936-07-17'), stop: new Date('1939-04-02')}, - {name: '1936 Summer Olympics', start: new Date('1936-08-01'), stop: new Date('1936-08-17')}, - {name: 'Hindenburg', start: new Date('1937-05-06'), stop: new Date('1937-05-07')}, - {name: 'World War Two', start: new Date('1939-09-01'), stop: new Date('1945-09-03')}, - {name: 'Pearl Harbour', start: new Date('1941-12-07'), stop: new Date('1941-12-08')}, - {name: 'Wannsee Conference', start: new Date('1942-01-20'), stop: new Date('1942-01-21')}, - {name: 'Battle of Stalingrad', start: new Date('1942-08-23'), stop: new Date('1943-02-03')}, - {name: 'D-Day', start: new Date('1944-06-06'), stop: new Date('1944-06-07')}, - {name: 'Liberation of Auschwitz', start: new Date('1945-01-27'), stop: new Date('1945-01-28')}, - {name: 'Hiroshima', start: new Date('1945-08-06'), stop: new Date('1945-08-07')}, - {name: 'Nagasaki', start: new Date('1945-08-09'), stop: new Date('1945-08-10')}, - {name: 'Nuremburg Trials', start: new Date('1945-11-20'), stop: new Date('1946-10-02')}, - {name: 'Fourth Republic', start: new Date('1946-10-14'), stop: new Date('1958-10-05')}, - {name: 'West Germany', start: new Date('1949-05-23'), stop: new Date('1990-10-03')}, - {name: 'Korean War', start: new Date('1950-06-25'), stop: new Date('1953-07-28')}, - {name: 'Cuban Revolution', start: new Date('1953-07-26'), stop: new Date('1959-01-02')}, - {name: 'Algerian War', start: new Date('1954-11-01'), stop: new Date('1962-03-20')}, - {name: 'Vietnam War', start: new Date('1955-11-01'), stop: new Date('1975-05-01')}, - {name: '1960s', start: new Date('1960-01-01'), stop: new Date('1970-01-01')}, - {name: 'Eichmann Trial', start: new Date('1961-04-11'), stop: new Date('1961-08-15')}, - {name: 'Bay of Pigs', start: new Date('1961-04-17'), stop: new Date('1961-04-20')}, - {name: 'Berlin Wall', start: new Date('1961-08-13'), stop: new Date('1989-11-09')}, - {name: 'Cuban Missile Crisis', start: new Date('1962-10'), stop: new Date('1962-12')}, - {name: 'Assassination of John F. Kennedy', start: new Date('1963-11-22'), stop: new Date('1963-11-23')}, - {name: 'Sgt. Pepper', start: new Date('1967-06-01'), stop: new Date('1967-06-02')}, - {name: 'Assassination of Benno Ohnesorg', start: new Date('1967-06-02'), stop: new Date('1967-06-03')}, - {name: 'Six-Day War', start: new Date('1967-06-05'), stop: new Date('1967-06-11')}, - {name: '1968', start: new Date('1968-01-01'), stop: new Date('1969-01-01')}, - {name: 'My Lai', start: new Date('1968-03-16'), stop: new Date('1968-03-17')}, - {name: 'Assassination of Martin Luther King', start: new Date('1968-04-04'), stop: new Date('1968-04-05')}, - {name: 'Assassination of Rudi Dutschke', start: new Date('1968-04-11'), stop: new Date('1968-04-12')}, - {name: 'May 1968', start: new Date('1968-05-01'), stop: new Date('1968-06-01')}, - {name: '1968 Cannes Film Festival', start: new Date('1968-05-10'), stop: new Date('1968-05-20')}, - {name: 'Valerie Solanas', start: new Date('1968-06-03'), stop: new Date('1968-06-04')}, - {name: 'Assassination of Robert F. Kennedy', start: new Date('1968-06-05'), stop: new Date('1968-06-06')}, - {name: '1968 Summer Olympics', start: new Date('1968-10-12'), stop: new Date('1968-10-28')}, - {name: 'Apollo 11', start: new Date('1969-07-16'), stop: new Date('1969-07-25')}, - {name: 'Moon Landing', start: new Date('1969-07-20'), stop: new Date('1969-07-21')}, - {name: 'The Epoch', start: new Date('1970-01-01 00:00:00'), stop: new Date('1970-01-01 00:00:01')}, - {name: '1970s', start: new Date('1970-01-01'), stop: new Date('1980-01-01')}, - {name: '1972 Summer Olympics', start: new Date('1972-08-26'), stop: new Date('1972-09-11')}, - {name: 'Apollo 17', start: new Date('1972-12-07'), stop: new Date('1972-12-20')}, - {name: 'World Trade Center', start: new Date('1973-04-04'), stop: new Date('2001-09-11')}, - {name: '1980s', start: new Date('1980-01-01'), stop: new Date('1990-01-01')}, - {name: 'Iran-Iraq War', start: new Date('1980-09-22'), stop: new Date('1988-08-21')}, - {name: 'Tschernobyl', start: new Date('1986-04-26'), stop: new Date('1986-04-27')}, - {name: 'Fall of the Berlin Wall', start: new Date('1989-11-09'), stop: new Date('1989-11-10')}, - {name: 'Gulf War', start: new Date('1990-08-02'), stop: new Date('1991-03-01')}, - {name: 'Clinton Presidency', start: new Date('1993-01-20'), stop: new Date('2001-01-20')}, - {name: 'German Reunification', start: new Date('1990-10-03'), stop: new Date('1990-10-04')}, - {name: 'The Battle of Seattle', start: new Date('1999-11-30'), stop: new Date('1999-12-01')}, - {name: 'George W. Bush', start: new Date('2001-01-20'), stop: new Date('2009-01-20')}, - {name: 'Carlo Giuliani', start: new Date('2001-07-20'), stop: new Date('2001-07-21')}, - {name: '9-11', start: new Date('2001-09-11'), stop: new Date('2001-09-12')}, - {name: 'Fukushima', start: new Date('2011-03-11'), stop: new Date('2011-03-12')} - ], - height: window.innerHeight, - width: window.innerWidth - }).appendTo(Ox.UI.$body); + Ox.theme('modern'); + + Ox.print('$$$$', Ox.Calendar) + + new Ox.Calendar({ + date: new Date(0), + dates: [ + {name: 'Genghis Khan', start: new Date('1162'), stop: new Date('1228'), type: 'Person'}, + {name: 'Marco Polo', start: new Date('1254'), stop: new Date('1324-01-09'), type: 'Person'}, + {name: 'Columbus', start: new Date('1451-08-22'), stop: new Date('1506-05-21'), type: 'Person'}, + {name: 'Da Vinci', start: new Date('1452-04-15'), stop: new Date('1519-05-03'), type: 'Person'}, + {name: 'Michelangelo', start: new Date('1475-03-06'), stop: new Date('1564-02-19'), type: 'Person'}, + {name: 'Bruegel', start: new Date('1525'), stop: new Date('1569-09-10'), type: 'Person'}, + {name: 'Galilei', start: new Date('1564-02-15'), stop: new Date('1642-01-09'), type: 'Person'}, + {name: 'Shakespeare', start: new Date('1564-04-26'), stop: new Date('1616-04-24'), type: 'Person'}, + {name: 'Descartes', start: new Date('1596-03-31'), stop: new Date('1650-02-12'), type: 'Person'}, + {name: 'Velazquez', start: new Date('1599-06-06'), stop: new Date('1660-08-07'), type: 'Person'}, + {name: 'Vermeer', start: new Date('1632-10-31'), stop: new Date('1675-12-16'), type: 'Person'}, + {name: 'Spinoza', start: new Date('1632-11-24'), stop: new Date('1677-02-22'), type: 'Person'}, + {name: 'Louix XIV', start: new Date('1638-09-05'), stop: new Date('1715-09-02'), type: 'Person'}, + {name: 'Newton', start: new Date('1643-01-04'), stop: new Date('1727-04-01'), type: 'Person'}, + {name: 'Leibniz', start: new Date('1646-07-01'), stop: new Date('1716-11-15'), type: 'Person'}, + {name: 'Kant', start: new Date('1724-04-22'), stop: new Date('1804-02-13'), type: 'Person'}, + {name: 'Napoleon', start: new Date('1769-08-15'), stop: new Date('1821-05-06'), type: 'Person'}, + {name: 'Hegel', start: new Date('1770-08-27'), stop: new Date('1831-11-15'), type: 'Person'}, + {name: 'Darwin', start: new Date('1809-02-12'), stop: new Date('1882-04-20'), type: 'Person'}, + {name: 'Marx', start: new Date('1818-05-05'), stop: new Date('1883-03-15'), type: 'Person'}, + /* + {name: 'Gandhi', start: new Date('1869-10-02'), stop: new Date('1948-01-31'), type: 'Person'}, + {name: 'Lenin', start: new Date('1870-04-22'), stop: new Date('1924-01-22'), type: 'Person'}, + {name: 'Stalin', start: new Date('1878-12-18'), stop: new Date('1953-03-06'), type: 'Person'}, + {name: 'Einstein', start: new Date('1879-03-14'), stop: new Date('1955-04-19'), type: 'Person'}, + {name: 'Picasso', start: new Date('1881-10-25'), stop: new Date('1973-04-09'), type: 'Person'}, + {name: 'Roosevelt', start: new Date('1882-01-30'), stop: new Date('1945-04-13'), type: 'Person'}, + {name: 'Hitler', start: new Date('1889-04-20'), stop: new Date('1945-05-01'), type: 'Person'}, + {name: 'Hitchcock', start: new Date('1899-08-13'), stop: new Date('1980-04-30'), type: 'Person'}, + {name: 'Turing', start: new Date('1912-06-23'), stop: new Date('1954-06-08'), type: 'Person'}, + {name: 'Kennedy', start: new Date('1917-05-29'), stop: new Date('1963-11-23'), type: 'Person'}, + {name: 'Deleuze', start: new Date('1925-01-18'), stop: new Date('1995-11-05'), type: 'Person'}, + {name: 'Warhol', start: new Date('1928-08-06'), stop: new Date('1987-02-23'), type: 'Person'}, + {name: 'Debord', start: new Date('1931-12-28'), stop: new Date('1994-12-01'), type: 'Person'}, + */ + {name: 'Test', start: new Date('1970-01-01'), stop: new Date('1970-01-02')}, + {name: 'Battle of Hastings', start: new Date('1066-10-14'), stop: new Date('1066-10-15')}, + {name: 'Renaissance', start: new Date('1300'), stop: new Date('1700')}, + {name: 'Spanish Inquisition', start: new Date('1480-09-27'), stop: new Date('1820-03-10')}, + {name: 'Discovery of America', start: new Date('1492'), stop: new Date('1493')}, + {name: 'East India Company', start: new Date('1600'), stop: new Date('1874')}, + {name: 'Thirty Years\' War', start: new Date('1618'), stop: new Date('1649')}, + {name: 'Declaration of Independence', start: new Date('1776-07-04'), stop: new Date('1776-07-05')}, + {name: 'French Revolution', start: new Date('1789'), stop: new Date('1800')}, + {name: 'Storming of the Bastille', start: new Date('1789-07-14'), stop: new Date('1789-07-15')}, + {name: 'Napoleonic Era', start: new Date('1799-11-09'), stop: new Date('1815-06-29')}, + {name: 'The Year Without a Summer', start: new Date('1816'), stop: new Date('1817')}, + {name: 'Queen Victoria', start: new Date('1837-06-20'), stop: new Date('1901-01-23')}, + {name: 'Crimean War', start: new Date('1853-10'), stop: new Date('1856-03')}, + {name: 'American Civil War', start: new Date('1861-04-12'), stop: new Date('1865-04-10')}, + {name: 'Franco-Prussian War', start: new Date('1870-07-19'), stop: new Date('1871-05-11')}, + {name: 'Paris Commune', start: new Date('1871-03-18'), stop: new Date('1871-05-29')}, + {name: 'Congo Free State', start: new Date('1885-07-11'), stop: new Date('1908-11-16')}, + {name: '20th century', start: new Date('1900'), stop: new Date('2000')}, + {name: 'Titanic', start: new Date('1912-04-15'), stop: new Date('1912-04-16')}, + {name: 'World War One', start: new Date('1914-07-28'), stop: new Date('1918-11-12')}, + {name: 'Battle of Verdun', start: new Date('1916-02-21'), stop: new Date('1916-12-19')}, + {name: 'Russian Revolution', start: new Date('1917'), stop: new Date('1918')}, + {name: 'October Revolution', start: new Date('1917-11-07'), stop: new Date('1917-11-09')}, + {name: 'Spanish Flu', start: new Date('1918'), stop: new Date('1919')}, + {name: 'Weimar Republic', start: new Date('1918-11-09'), stop: new Date('1933-01-30')}, + {name: 'Treaty of Versailles', start: new Date('1919-06-28'), stop: new Date('1919-06-29')}, + {name: '1920s', start: new Date('1920'), stop: new Date('1930')}, + {name: 'Soviet Union', start: new Date('1922-12-30'), stop: new Date('1991-12-27')}, + {name: 'The Third Reich', start: new Date('1933-01-30'), stop: new Date('1945-05-09')}, + {name: 'Spanish Civil War', start: new Date('1936-07-17'), stop: new Date('1939-04-02')}, + {name: '1936 Summer Olympics', start: new Date('1936-08-01'), stop: new Date('1936-08-17')}, + {name: 'Hindenburg', start: new Date('1937-05-06'), stop: new Date('1937-05-07')}, + {name: 'World War Two', start: new Date('1939-09-01'), stop: new Date('1945-09-03')}, + {name: 'Pearl Harbour', start: new Date('1941-12-07'), stop: new Date('1941-12-08')}, + {name: 'Wannsee Conference', start: new Date('1942-01-20'), stop: new Date('1942-01-21')}, + {name: 'Battle of Stalingrad', start: new Date('1942-08-23'), stop: new Date('1943-02-03')}, + {name: 'D-Day', start: new Date('1944-06-06'), stop: new Date('1944-06-07')}, + {name: 'Liberation of Auschwitz', start: new Date('1945-01-27'), stop: new Date('1945-01-28')}, + {name: 'Hiroshima', start: new Date('1945-08-06'), stop: new Date('1945-08-07')}, + {name: 'Nagasaki', start: new Date('1945-08-09'), stop: new Date('1945-08-10')}, + {name: 'Nuremburg Trials', start: new Date('1945-11-20'), stop: new Date('1946-10-02')}, + {name: 'Fourth Republic', start: new Date('1946-10-14'), stop: new Date('1958-10-05')}, + {name: 'West Germany', start: new Date('1949-05-23'), stop: new Date('1990-10-03')}, + {name: 'Korean War', start: new Date('1950-06-25'), stop: new Date('1953-07-28')}, + {name: 'Cuban Revolution', start: new Date('1953-07-26'), stop: new Date('1959-01-02')}, + {name: 'Algerian War', start: new Date('1954-11-01'), stop: new Date('1962-03-20')}, + {name: 'Vietnam War', start: new Date('1955-11-01'), stop: new Date('1975-05-01')}, + {name: '1960s', start: new Date('1960-01-01'), stop: new Date('1970-01-01')}, + {name: 'Eichmann Trial', start: new Date('1961-04-11'), stop: new Date('1961-08-15')}, + {name: 'Bay of Pigs', start: new Date('1961-04-17'), stop: new Date('1961-04-20')}, + {name: 'Berlin Wall', start: new Date('1961-08-13'), stop: new Date('1989-11-09')}, + {name: 'Cuban Missile Crisis', start: new Date('1962-10'), stop: new Date('1962-12')}, + {name: 'Assassination of John F. Kennedy', start: new Date('1963-11-22'), stop: new Date('1963-11-23')}, + {name: 'Sgt. Pepper', start: new Date('1967-06-01'), stop: new Date('1967-06-02')}, + {name: 'Assassination of Benno Ohnesorg', start: new Date('1967-06-02'), stop: new Date('1967-06-03')}, + {name: 'Six-Day War', start: new Date('1967-06-05'), stop: new Date('1967-06-11')}, + {name: '1968', start: new Date('1968-01-01'), stop: new Date('1969-01-01')}, + {name: 'My Lai', start: new Date('1968-03-16'), stop: new Date('1968-03-17')}, + {name: 'Assassination of Martin Luther King', start: new Date('1968-04-04'), stop: new Date('1968-04-05')}, + {name: 'Assassination of Rudi Dutschke', start: new Date('1968-04-11'), stop: new Date('1968-04-12')}, + {name: 'May 1968', start: new Date('1968-05-01'), stop: new Date('1968-06-01')}, + {name: '1968 Cannes Film Festival', start: new Date('1968-05-10'), stop: new Date('1968-05-20')}, + {name: 'Valerie Solanas', start: new Date('1968-06-03'), stop: new Date('1968-06-04')}, + {name: 'Assassination of Robert F. Kennedy', start: new Date('1968-06-05'), stop: new Date('1968-06-06')}, + {name: '1968 Summer Olympics', start: new Date('1968-10-12'), stop: new Date('1968-10-28')}, + {name: 'Apollo 11', start: new Date('1969-07-16'), stop: new Date('1969-07-25')}, + {name: 'Moon Landing', start: new Date('1969-07-20'), stop: new Date('1969-07-21')}, + {name: 'The Epoch', start: new Date('1970-01-01 00:00:00'), stop: new Date('1970-01-01 00:00:01')}, + {name: '1970s', start: new Date('1970-01-01'), stop: new Date('1980-01-01')}, + {name: '1972 Summer Olympics', start: new Date('1972-08-26'), stop: new Date('1972-09-11')}, + {name: 'Apollo 17', start: new Date('1972-12-07'), stop: new Date('1972-12-20')}, + {name: 'World Trade Center', start: new Date('1973-04-04'), stop: new Date('2001-09-11')}, + {name: '1980s', start: new Date('1980-01-01'), stop: new Date('1990-01-01')}, + {name: 'Iran-Iraq War', start: new Date('1980-09-22'), stop: new Date('1988-08-21')}, + {name: 'Tschernobyl', start: new Date('1986-04-26'), stop: new Date('1986-04-27')}, + {name: 'Fall of the Berlin Wall', start: new Date('1989-11-09'), stop: new Date('1989-11-10')}, + {name: 'Gulf War', start: new Date('1990-08-02'), stop: new Date('1991-03-01')}, + {name: 'Clinton Presidency', start: new Date('1993-01-20'), stop: new Date('2001-01-20')}, + {name: 'German Reunification', start: new Date('1990-10-03'), stop: new Date('1990-10-04')}, + {name: 'The Battle of Seattle', start: new Date('1999-11-30'), stop: new Date('1999-12-01')}, + {name: 'George W. Bush', start: new Date('2001-01-20'), stop: new Date('2009-01-20')}, + {name: 'Carlo Giuliani', start: new Date('2001-07-20'), stop: new Date('2001-07-21')}, + {name: '9-11', start: new Date('2001-09-11'), stop: new Date('2001-09-12')}, + {name: 'Fukushima', start: new Date('2011-03-11'), stop: new Date('2011-03-12')} + ], + height: window.innerHeight, + width: window.innerWidth + }).appendTo(Ox.UI.$body); + + }); }); \ No newline at end of file diff --git a/source/js/Ox.AnnotationPanel.js b/source/js/Ox.AnnotationPanel.js new file mode 100644 index 00000000..50cec2f0 --- /dev/null +++ b/source/js/Ox.AnnotationPanel.js @@ -0,0 +1,117 @@ +Ox.AnnotationPanel = function(options, self) { + + var self = self || {}, + that = new Ox.Element('div', self) + .defaults({ + id: '', + items: [], + title: '', + type: 'text', + width: 0 + }) + .options(options || {}); + + self.selected = -1; + + that.$element = new Ox.CollapsePanel({ + collapsed: false, + extras: [ + new Ox.Button({ + id: 'add', + style: 'symbol', + title: 'Add', + type: 'image' + }).bindEvent({ + click: function(event, data) { + that.triggerEvent('add', {value: ''}); + } + }) + ], + size: 16, + title: self.options.title + }) + .addClass('OxAnnotationPanel') + .bindEvent({ + toggle: togglePanel + }); + that.$content = that.$element.$content; + + self.$annotations = new Ox.List({ + construct: function(data) { + return new Ox.Element('div') + .addClass('OxAnnotation OxEditable OxTarget') + .html(Ox.parseHTML(data.value)); + }, + items: $.map(self.options.items, function(v, i) { + return { + id: v.id || i + '', + value: v.value + }; + }), + unique: 'id' + }) + .bindEvent({ + open: function(event, data) { + if (data.ids.length == 1) { + var pos = Ox.getPositionById(self.$annotations.options('items'), data.ids[0]); + self.$annotations.editItem(pos); + + } + }, + 'delete': function(event, data) { + that.triggerEvent('delete', data); + }, + select: selectAnnotation, + submit: updateAnnotation + }) + .appendTo(that.$content); + + /* + self.$annotations = new Ox.Element('div') + .appendTo(that.$content); + self.$annotation = []; + self.options.items.forEach(function(item, i) { + self.$annotation[i] = new Ox.Element('div') + .addClass('OxAnnotation') + .html(item.value.replace(/\n/g, '
')) + .click(function() { + clickAnnotation(i); + }) + .appendTo(self.$annotations); + }); + */ + function selectAnnotation(event, data) { + var item = Ox.getObjectById(self.options.items, data.ids[0]); + that.triggerEvent('select', { + 'in': item['in'], + 'out': item.out, + 'layer': self.options.id + }); + } + function updateAnnotation(event, data) { + var item = Ox.getObjectById(self.options.items, data.id); + item.value = data.value; + that.triggerEvent('submit', item); + } + + function togglePanel() { + + } + + that.addItem = function(item) { + var pos = 0; + self.options.items.splice(pos, 0, item); + self.$annotations.addItems(pos, [item]); + self.$annotations.editItem(pos); + } + + that.removeItems = function(ids) { + self.$annotations.removeItems(ids); + } + that.deselectItems = function() { + if(self.$annotations.options('selected')) + self.$annotations.options('selected',[]); + } + return that; + +}; diff --git a/source/js/Ox.App.js b/source/js/Ox.App.js new file mode 100644 index 00000000..ee9bb7e5 --- /dev/null +++ b/source/js/Ox.App.js @@ -0,0 +1,200 @@ +/* +============================================================================ +Application +============================================================================ +*/ + +// fixme: get rid og launch, fire load event + +Ox.App = (function() { + + /*** + Ox.App + Basic application instance that communicates with a JSON API. + The JSON API should support at least the following actions: + api returns all api methods + init returns {config: {...}, user: {...}} + Options + timeout API timeout in msec + type 'GET' or 'POST' + url URL of the API + Methods + api[action] make a request + api.cancel cancel a request + launch launch the App + options get or set options + ***/ + + return function(options) { + + options = options || {}; + var self = {}, + that = this; + + self.time = +new Date(); + + self.options = $.extend({ + timeout: 60000, + type: 'POST', + url: '/api/', + }, options); + + that.$element = new Ox.Element('body'); + + function getUserAgent() { + var userAgent = ''; + Ox.forEach(['Chrome', 'Firefox', 'Internet Explorer', 'Opera', 'Safari'], function(v) { + if (navigator.userAgent.indexOf(v) > -1) { + userAgent = v; + return false; + } + }); + if (!userAgent && $.browser.mozilla) { + userAgent = 'Firefox'; + } + if (!userAgent && $.browser.webkit) { + userAgent = 'Chrome'; + } + return userAgent; + } + + function getUserData() { + return { + document: { + referrer: document.referrer + }, + history: { + length: history.length + }, + navigator: { + cookieEnabled: navigator.cookieEnabled, + plugins: $.map(navigator.plugins, function(plugin, i) { + return plugin.name; + }), + userAgent: navigator.userAgent + }, + screen: screen, + time: (+new Date() - self.time) / 1000, + window: { + innerHeight: window.innerHeight, + innerWidth: window.innerWidth, + outerHeight: window.outerHeight, + outerWidth: window.outerWidth, + screenLeft: window.screenLeft, + screenTop: window.screenTop + } + }; + } + + function loadImages(callback) { + window.OxImageCache = []; + $.getJSON(Ox.UI.PATH + 'json/ox.ui.images.json', function(data) { + // fixme: find a better way to not wait for flags + data = data.filter(function(image) { + return !Ox.startsWith(image, 'svg/ox.map/') + }); + var counter = 0, + length = data.length; + data.forEach(function(src, i) { + var image = new Image(); + image.src = Ox.UI.PATH + src; + image.onload = function() { + (++counter == length) && callback(); + } + window.OxImageCache.push(image); // fixme: global var??? + }); + }); + } + + self.change = function(key, value) { + + }; + + that.api = { + api: function(callback) { + Ox.Request.send({ + url: self.options.url, + data: { + action: 'api' + }, + callback: callback + }); + }, + cancel: function(id) { + Ox.Request.cancel(id); + } + }; + + that.bindEvent = function() { + + }; + + // fixme: use $.when() + that.launch = function(callback) { + var time = +new Date(), + userAgent = getUserAgent(), + userAgents = ['Chrome', 'Firefox', 'Opera', 'Safari']; + $.ajaxSetup({ + timeout: self.options.timeour, + type: self.options.type, + url: self.options.url + }); + userAgents.indexOf(userAgent) > -1 ? start() : stop(); + function start() { + // fixme: rename config to site? + var counter = 0, config, user; + that.api.api(function(result) { + Ox.forEach(result.data.actions, function(val, key) { + that.api[key] = function(data, callback) { + if (arguments.length == 1 && Ox.isFunction(data)) { + callback = data; + data = {}; + } + return Ox.Request.send($.extend({ + url: self.options.url, + data: { + action: key, + data: JSON.stringify(data) + }, + callback: callback + }, !val.cache ? {age: 0}: {})); + }; + }); + that.api.init(getUserData(), function(result) { + config = result.data.config; + user = result.data.user; + // fixme: not generic + document.title = config.site.name; + launchCallback(); + }); + }); + loadImages(launchCallback); + function launchCallback() { + ++counter == 2 && $(function() { + var $div = Ox.UI.$body.find('div'); + Ox.UI.$body.find('img').remove(); + $div.animate({ + opacity: 0 + }, 1000, function() { + $div.remove(); + }); + // fixme: not generic enough, just pass data + callback({config: config, user: user}); + }); + } + } + function stop() { + that.request.send(self.options.init, getUserData(), function() {}); + } + return that; + }; + + that.options = function() { + return Ox.getset(self.options, Array.prototype.slice.call(arguments), self.change, that); + }; + + return that; + + }; + +}()); diff --git a/source/js/Ox.Bar.js b/source/js/Ox.Bar.js new file mode 100644 index 00000000..7fb55c2b --- /dev/null +++ b/source/js/Ox.Bar.js @@ -0,0 +1,18 @@ +/** +*/ +Ox.Bar = function(options, self) { + var self = self || {}, + that = new Ox.Element({}, self) + .defaults({ + orientation: 'horizontal', + size: 'medium' // can be int + }) + .options(options || {}) + .addClass('OxBar Ox' + Ox.toTitleCase(self.options.orientation)), + dimensions = Ox.UI.DIMENSIONS[self.options.orientation]; + self.options.size = Ox.isString(self.options.size) ? + Ox.UI.getBarSize(self.options.size) : self.options.size; + that.css(dimensions[0], '100%') + .css(dimensions[1], self.options.size + 'px'); + return that; +}; diff --git a/source/js/Ox.BlockTimeline.js b/source/js/Ox.BlockTimeline.js new file mode 100644 index 00000000..eec9a0f2 --- /dev/null +++ b/source/js/Ox.BlockTimeline.js @@ -0,0 +1,363 @@ +Ox.BlockTimeline = function(options, self) { + + var self = self || {}, + that = new Ox.Element('div', self) + .defaults({ + cuts: [], + duration: 0, + find: '', + matches: [], + points: [0, 0], + position: 0, + subtitles: [], + videoId: '', + width: 0 + }) + .options(options || {}) + .addClass('OxTimelineSmall') + .mousedown(mousedown) + .mouseleave(mouseleave) + .mousemove(mousemove) + .bindEvent({ + drag: function(event, e) { + mousedown(e); + } + }); + + $.extend(self, { + $images: [], + $lines: [], + $markerPoint: [], + $selection: [], + $subtitles: [], + $tooltip: new Ox.Tooltip({ + animate: false + }).css({ + textAlign: 'center' + }), + hasSubtitles: self.options.subtitles.length, + height: 16, + lines: Math.ceil(self.options.duration / self.options.width), + margin: 8 + }); + + that.css({ + width: (self.options.width + self.margin) + 'px', + height: ((self.height + self.margin) * self.lines + 4) + 'px' + }); + + getTimelineImageURL(function(url) { + + self.timelineImageURL = url; + + Ox.range(0, self.lines).forEach(function(i) { + addLine(i); + }); + + self.$markerPosition = $('') + .addClass('OxMarkerPosition') + .attr({ + src: Ox.UI.PATH + 'png/ox.ui/videoMarkerPlay.png' + }) + .css({ + position: 'absolute', + width: '9px', + height: '5px', + zIndex: 10 + }) + .appendTo(that.$element); + + setPosition(); + + ['in', 'out'].forEach(function(v, i) { + var titleCase = Ox.toTitleCase(v); + self.$markerPoint[i] = $('') + .addClass('OxMarkerPoint' + titleCase) + .attr({ + src: Ox.UI.PATH + 'png/ox.ui/videoMarker' + titleCase + '.png' + }) + .appendTo(that.$element); + setMarkerPoint(i); + }); + + }); + + function addLine(i) { + // fixme: get URLs once, not once for every line + self.$lines[i] = new Ox.Element('div') + .css({ + top: i * (self.height + self.margin) + 'px', + width: self.options.width + 'px' + }) + .appendTo(that); + self.$images[i] = $('') + .addClass('OxTimelineSmallImage') + .attr({ + src: self.timelineImageURL + }) + .css({ + marginLeft: (-i * self.options.width) + 'px' + }) + .appendTo(self.$lines[i].$element) + if (self.hasSubtitles) { + self.subtitlesImageURL = getSubtitlesImageURL(); + self.$subtitles[i] = $('') + .addClass('OxTimelineSmallSubtitles') + .attr({ + src: self.subtitlesImageURL + }) + .css({ + marginLeft: (-i * self.options.width) + 'px' + }) + .appendTo(self.$lines[i].$element); + } + if (self.options.points[0] != self.options.points[1]) { + addSelection[i]; + } + } + + function addSelection(i) { + self.selectionImageURL = getSelectionImageURL(); + self.$selection[i] && self.$selection[i].remove(); + self.$selection[i] = $('') + .addClass('OxTimelineSmallSelection') + .attr({ + src: self.selectionImageURL + }) + .css({ + marginLeft: (-i * self.options.width) + 'px' + }) + .appendTo(self.$lines[i].$element); + } + + function getPosition(e) { + //FIXME: this might still be broken in opera according to http://acko.net/blog/mouse-handling-and-absolute-positions-in-javascript + return (e.offsetX ? e.offsetX : e.clientX - $(e.target).offset().left); + } + + function getSelectionImageURL() { + var height = 18, + width = Math.ceil(self.options.duration), + $canvas = $('') + .attr({ + height: height, + width: width + }), + canvas = $canvas[0], + context = canvas.getContext('2d'), + imageData = context.createImageData(width, height), + data = imageData.data, + points = $.map(self.options.points, function(v, i) { + return Math.round(v) + i; + }), + top = 0, + bottom = 18; + Ox.range(points[0], points[1]).forEach(function(x) { + Ox.range(top, bottom).forEach(function(y) { + var color = (y == top || y == bottom - 1) ? [255, 255, 255, 255] : [255, 255, 255, 64], + index = x * 4 + y * 4 * width; + data[index] = color[0]; + data[index + 1] = color[1]; + data[index + 2] = color[2]; + data[index + 3] = color[3] + }); + }); + context.putImageData(imageData, 0, 0); + return canvas.toDataURL(); + } + + function getSubtitle(position) { + var subtitle = null; + Ox.forEach(self.options.subtitles, function(v) { + if (v['in'] <= position && v['out'] >= position) { + subtitle = v; + return false; + } + }); + return subtitle; + } + + function getSubtitlesImageURL() { + var height = 18, + width = Math.ceil(self.options.duration), + $canvas = $('') + .attr({ + height: height, + width: width + }), + canvas = $canvas[0], + context = canvas.getContext('2d'), + imageData = context.createImageData(width, height), + data = imageData.data; + self.options.subtitles.forEach(function(v) { + //var color = self.options.matches.indexOf(i) > -1 ? [255, 255, 0] : [255, 255, 255] + var inPoint = Math.round(v['in']), + outPoint = Math.round(v.out) + 1, + lines = v.value.split('\n').length, + bottom = 15, + top = bottom - lines - 2; + Ox.range(inPoint, outPoint).forEach(function(x) { + Ox.range(top, bottom).forEach(function(y) { + var color = (y == top || y == bottom - 1) ? [0, 0, 0] : [255, 255, 255], + index = x * 4 + y * 4 * width; + data[index] = color[0]; + data[index + 1] = color[1]; + data[index + 2] = color[2]; + data[index + 3] = 128 + }); + }); + }); + context.putImageData(imageData, 0, 0); + return canvas.toDataURL(); + } + + function getTimelineImageURL(callback) { + var height = 16, + images = Math.ceil(self.options.duration / 3600), + loaded = 0, + width = Math.ceil(self.options.duration), + $canvas = $('') + .attr({ + height: height, + width: width + }), + canvas = $canvas[0], + context = canvas.getContext('2d'); + Ox.range(images).forEach(function(i) { + var $img = $('') + .attr({ + src: '/' + self.options.videoId + '/timelines/timeline.16.' + i + '.png' + }) + .load(function() { + context.drawImage($img[0], i * 3600, 0); + //Ox.print('loaded, images', loaded, images, $img[0]) + if (++loaded == images) { + //Ox.print('callback', canvas.toDataURL().length) + callback(canvas.toDataURL()); + } + }); + }); + } + + function mousedown(e) { + var $target = $(e.target); + if ( + $target.hasClass('OxTimelineSmallImage') || + $target.hasClass('OxTimelineSmallSubtitles') || + $target.hasClass('OxTimelineSmallSelection') + ) { + self.options.position = getPosition(e); + setPosition(); + that.triggerEvent('change', { + position: self.options.position + }); + } + e.preventDefault(); + } + + function mouseleave(e) { + self.$tooltip.hide(); + } + + function mousemove(e) { + var $target = $(e.target), + position, + subtitle; + if ( + $target.hasClass('OxTimelineSmallImage') || + $target.hasClass('OxTimelineSmallSubtitles') || + $target.hasClass('OxTimelineSmallSelection') + ) { + position = getPosition(e), + subtitle = getSubtitle(position); + self.$tooltip.options({ + title: subtitle ? + '' + + Ox.highlight(subtitle.value, self.options.find).replace(/\n/g, '
') + '

' + + Ox.formatDuration(subtitle['in'], 3) + ' - ' + Ox.formatDuration(subtitle['out'], 3) : + Ox.formatDuration(position, 3) + }) + .show(e.clientX, e.clientY); + } else { + self.$tooltip.hide(); + } + + } + + function setMarker() { + self.$markerPosition + .css({ + left: (self.options.position % self.options.width) + 'px', + top: (parseInt(self.options.position / self.options.width) * (self.height + self.margin) + 2) + 'px', + }); + } + + function setMarkerPoint(i) { + var position = Math.round(self.options.points[i]); + self.$markerPoint[i] + .css({ + left: (position % self.options.width) + 'px', + top: (parseInt(position / self.options.width) * (self.height + self.margin) + 16) + 'px', + }); + } + + function setPosition() { + self.options.position = Ox.limit(self.options.position, 0, self.options.duration); + setMarker(); + } + + function setWidth() { + self.lines = Math.ceil(self.options.duration / self.options.width); + that.css({ + width: (self.options.width + self.margin) + 'px', + height: ((self.height + self.margin) * self.lines + 4) + 'px' + }); + Ox.range(self.lines).forEach(function(i) { + if (self.$lines[i]) { + self.$lines[i].css({ + width: self.options.width + 'px' + }); + self.$images[i].css({ + marginLeft: (-i * self.options.width) + 'px' + }); + if (self.hasSubtitles) { + self.$subtitles[i].css({ + marginLeft: (-i * self.options.width) + 'px' + }); + } + } else { + addLine(i); + } + }); + while (self.$lines.length > self.lines) { + self.$lines[self.$lines.length - 1].removeElement(); + self.$lines.pop(); + } + setMarker(); + setMarkerPoint(0); + setMarkerPoint(1); + } + + function updateSelection() { + self.$lines.forEach(function($line, i) { + addSelection(i); + }); + } + + self.onChange = function(key, value) { + //Ox.print('onChange:', key, value) + if (key == 'points') { + //Ox.print('key', key, 'value', value) + setMarkerPoint(0); + setMarkerPoint(1); + updateSelection() + } else if (key == 'position') { + setPosition(); + } else if (key == 'width') { + setWidth(); + } + }; + + return that; + +}; diff --git a/source/js/Ox.Button.js b/source/js/Ox.Button.js new file mode 100644 index 00000000..bc95302b --- /dev/null +++ b/source/js/Ox.Button.js @@ -0,0 +1,163 @@ +Ox.Button = function(options, self) { + + /** + methods: + toggleDisabled enable/disable button + toggleSelected select/unselect button + toggleTitle if more than one title was provided, + toggle to next title. + events: + click non-selectable button was clicked + deselect selectable button was deselected + select selectable button was selected + */ + + var self = self || {}, + that = new Ox.Element('input', self) + .defaults({ + disabled: false, + group: false, + id: '', + overlap: 'none', + selectable: false, + selected: false, + size: 'medium', + // fixme: 'default' or ''? + style: 'default', // can be default, checkbox, symbol, or tab + title: '', + tooltip: '', + type: 'text', + width: 'auto' + }) + .options(options || {}) + .attr({ + disabled: self.options.disabled ? 'disabled' : '', + type: self.options.type == 'text' ? 'button' : 'image' + }) + .addClass('OxButton Ox' + Ox.toTitleCase(self.options.size) + + (self.options.disabled ? ' OxDisabled': '') + + (self.options.selected ? ' OxSelected': '') + + (self.options.style != 'default' ? ' Ox' + Ox.toTitleCase(self.options.style) : '') + + (self.options.overlap != 'none' ? ' OxOverlap' + Ox.toTitleCase(self.options.overlap) : '')) + .css(self.options.width == 'auto' ? {} : { + width: (self.options.width - 14) + 'px' + }) + .mousedown(mousedown) + .click(click); + + $.extend(self, Ox.isArray(self.options.title) ? { + selectedTitle: Ox.setPropertyOnce(self.options.title, 'selected'), + titles: self.options.title + } : { + selectedTitle: 0, + titles: [{ + id: '', + title: self.options.title + }] + }); + + setTitle(self.titles[self.selectedTitle].title); + + if (self.options.tooltip) { + self.tooltips = Ox.isArray(self.options.tooltip) ? self.options.tooltip : [self.options.tooltip]; + self.$tooltip = new Ox.Tooltip({ + title: self.tooltips[self.selectedTitle] + }); + that.mouseenter(mouseenter) + .mouseleave(mouseleave); + } + + function click() { + if (!self.options.disabled) { + var data = self.titles[self.selectedTitle]; + if (!self.options.selectable) { + that.triggerEvent('click', data); + } else { + //self.options.selected = !self.options.selected; + //that.toggleClass('OxSelected'); + if (self.options.group) { + that.triggerEvent('select', data); + } else { + that.toggleSelected(); + //that.triggerEvent('change', {selected: self.options.selected}); + } + } + if (self.titles.length == 2) { + that.toggleTitle(); + } + } + } + + function mousedown(e) { + if (self.options.type == 'image' && $.browser.safari) { + // keep image from being draggable + e.preventDefault(); + } + } + + function mouseenter(e) { + self.$tooltip.show(e.clientX, e.clientY); + } + + function mouseleave() { + self.$tooltip.hide(); + } + + function setTitle(title) { + self.title = title; + if (self.options.type == 'image') { + that.attr({ + src: Ox.UI.getImagePath( + 'symbol' + title[0].toUpperCase() + title.substr(1) + '.svg' + ) + }); + } else { + that.val(title); + } + } + + self.onChange = function(key, value) { + if (key == 'disabled') { + that.attr({ + disabled: value ? 'disabled' : '' + }) + .toggleClass('OxDisabled'); + } else if (key == 'selected') { + if (value != that.hasClass('OxSelected')) { // fixme: neccessary? + that.toggleClass('OxSelected'); + } + that.triggerEvent('change'); + } else if (key == 'title') { + setTitle(value); + } else if (key == 'width') { + that.$element.css({ + width: (value - 14) + 'px' + }); + } + } + + that.toggleDisabled = function() { + that.options({ + enabled: !self.options.disabled + }); + //self.options.disabled = !self.options.disabled; + } + + that.toggleSelected = function() { + that.options({ + selected: !self.options.selected + }); + //self.options.selected = !self.options.selected; + } + + that.toggleTitle = function() { + self.selectedTitle = 1 - self.selectedTitle; + setTitle(self.titles[self.selectedTitle].title); + self.$tooltip && self.$tooltip.options({ + title: self.tooltips[self.selectedTitle] + }); + } + + return that; + +}; diff --git a/source/js/Ox.ButtonGroup.js b/source/js/Ox.ButtonGroup.js new file mode 100644 index 00000000..b8077d1d --- /dev/null +++ b/source/js/Ox.ButtonGroup.js @@ -0,0 +1,74 @@ +Ox.ButtonGroup = function(options, self) { + + /** + options + buttons array of buttons + max integer, maximum number of selected buttons, 0 for all + min integer, minimum number of selected buttons, 0 for none + selectable if true, buttons are selectable + type string, 'image' or 'text' + methods: + events: + change {id, value} selection within a group changed + */ + + var self = self || {}, + that = new Ox.Element({}, self) + .defaults({ + buttons: [], + max: 1, + min: 1, + selectable: false, + size: 'medium', + style: '', + type: 'text', + }) + .options(options || {}) + .addClass('OxButtonGroup'); + + if (self.options.selectable) { + self.optionGroup = new Ox.OptionGroup( + self.options.buttons, + self.options.min, + self.options.max, + 'selected' + ); + self.options.buttons = self.optionGroup.init(); + } + + self.$buttons = []; + self.options.buttons.forEach(function(button, position) { + var id = self.options.id + Ox.toTitleCase(button.id) + self.$buttons[position] = Ox.Button({ + disabled: button.disabled, + group: true, + id: id, + selectable: self.options.selectable, + selected: button.selected, + size: self.options.size, + style: self.options.style, + title: button.title, + type: self.options.type + }) + .bindEvent('select', function() { + selectButton(position); + }) + .appendTo(that); + }); + + function selectButton(pos) { + var toggled = self.optionGroup.toggle(pos); + if (toggled.length) { + toggled.forEach(function(pos, i) { + self.$buttons[pos].toggleSelected(); + }); + that.triggerEvent('change', { + selected: $.map(self.optionGroup.selected(), function(v, i) { + return self.options.buttons[v].id; + }) + }); + } + } + + return that; +}; diff --git a/source/js/Ox.Calendar.js b/source/js/Ox.Calendar.js new file mode 100644 index 00000000..4819ebfc --- /dev/null +++ b/source/js/Ox.Calendar.js @@ -0,0 +1,711 @@ +Ox.Calendar = function(options, self) { + + self = self || {}; + var that = new Ox.Element({}, self) + .defaults({ + date: new Date(), + dates: [], + height: 512, + range: [100, 5101], + width: 512, + zoom: 8 + }) + .options(options || {}) + .addClass('OxCalendar') + .css({ + width: self.options.width + 'px', + height: self.options.height + 'px' + }); + + self.maxZoom = 32; + self.minLabelWidth = 80; + self.overlayWidths = [Math.round(self.options.width / 16)]; + self.overlayWidths = [ + Math.floor((self.options.width - self.overlayWidths[0]) / 2), + self.overlayWidths[0], + Math.ceil((self.options.width - self.overlayWidths[0]) / 2), + ]; + self.units = [ + { + id: 'millennium', + seconds: 365242.5 * 86400, + date: function(i) { + return new Date((i + 1) + '000'); + }, + name: function(i) { + return Ox.formatOrdinal(i + 2) + ' millennium'; + }, + value: function(date) { + return Math.floor(date.getFullYear() / 1000) - 1; + } + }, + { + id: 'century', + seconds: 36524.25 * 86400, + date: function(i) { + return new Date((i + 19) + '00'); + }, + name: function(i) { + return Ox.formatOrdinal(i + 20) + ' century'; + }, + value: function(date) { + return Math.floor(date.getFullYear() / 100) - 19; + } + }, + { + id: 'decade', + seconds: 3652.425 * 86400, + date: function(i) { + return (i + 197) + '0' + }, + name: function(i) { + return (i + 197) + '0s' + }, + value: function(date) { + return Math.floor(date.getFullYear() / 10) - 197; + } + }, + { + id: 'year', + seconds: 365.2425 * 86400, + date: function(i) { + return (i + 1970) + ''; + }, + name: function(i) { + return (i + 1970) + ''; + }, + value: function(date) { + return date.getFullYear() - 1970; + } + }, + { + id: 'month', + seconds: 365.2425 / 12 * 86400, + date: function(i) { + return (Math.floor(i / 12) + 1970) + '-' + (Ox.mod(i, 12) + 1); + }, + name: function(i) { + return Ox.SHORT_MONTHS[Ox.mod(i, 12)] + ' ' + Math.floor(i / 12 + 1970) + }, + value: function(date) { + return (date.getFullYear() - 1970) * 12 + date.getMonth(); + } + }, + { + id: 'week', + seconds: 7 * 86400, + date: function(i) { + return (i * 7 - 3) * 86400000; + }, + name: function(i) { + return Ox.formatDate(new Date((i * 7 - 3) * 86400000), '%a, %b %e'); + }, + value: function(date) { + return Math.floor((+date / 86400000 + 4) / 7); + } + }, + { + id: 'day', + seconds: 86400, + date: function(i) { + // adjust for timezone difference + // fixme: may still be off + return i * 86400000 + Ox.TIMEZONE_OFFSET; + }, + name: function(i) { + return Ox.formatDate(new Date(i * 86400000), '%b %e, %Y'); + }, + value: function(date) { + return Math.floor(date / 86400000); + } + }, + { + id: 'six_hours', + seconds: 21600, + date: function(i) { + return i * 21600000 + Ox.TIMEZONE_OFFSET; + }, + name: function(i) { + return Ox.formatDate(new Date(i * 21600000 + Ox.TIMEZONE_OFFSET), '%b %e, %H:00'); + }, + value: function(date) { + return Math.floor((date - Ox.TIMEZONE_OFFSET) / 21600000); + } + }, + { + id: 'hour', + seconds: 3600, + date: function(i) { + return i * 3600000; + }, + name: function(i) { + return Ox.formatDate(new Date(i * 3600000), '%b %e, %H:00'); + }, + value: function(date) { + return Math.floor(date / 3600000); + } + }, + { + id: 'five_minutes', + seconds: 300, + date: function(i) { + return i * 300000; + }, + name: function(i) { + return Ox.formatDate(new Date(i * 300000), '%b %e, %H:%M'); + }, + value: function(date) { + return Math.floor(date / 300000); + } + }, + { + id: 'minute', + seconds: 60, + date: function(i) { + return i * 60000; + }, + name: function(i) { + return Ox.formatDate(new Date(i * 60000), '%b %e, %H:%M'); + }, + value: function(date) { + return Math.floor(date / 60000); + } + }, + { + id: 'five_seconds', + seconds: 5, + date: function(i) { + return i * 5000; + }, + name: function(i) { + return Ox.formatDate(new Date(i * 5000), '%H:%M:%S'); + }, + value: function(date) { + return Math.floor(date / 5000); + } + }, + { + id: 'second', + seconds: 1, + date: function(i) { + return i * 1000; + }, + name: function(i) { + return Ox.formatDate(new Date(i * 1000), '%H:%M:%S'); + }, + value: function(date) { + return Math.floor(date / 1000); + } + } + ]; + + self.$container = new Ox.Element() + .addClass('OxCalendarContainer') + .css({ + top: '24px', + bottom: '40px' + }) + .bind({ + mouseleave: mouseleave, + mousemove: mousemove, + mousewheel: mousewheel + }) + .bindEvent({ + doubleclick: doubleclick, + dragstart: dragstart, + drag: drag, + dragpause: dragpause, + dragend: dragend, + singleclick: singleclick + }) + .appendTo(that); + + self.$content = new Ox.Element() + .addClass('OxCalendarContent') + .appendTo(self.$container); + + self.$background = new Ox.Element() + .addClass('OxBackground') + .appendTo(self.$content); + + self.$scalebar = new Ox.Element() + .addClass('OxTimeline') + .css({ + posision: 'absolute', + }) + .appendTo(self.$content); + + self.$scrollbar = new Ox.Element() + .addClass('OxTimeline') + .css({ + posision: 'absolute', + bottom: '40px' + }) + .appendTo(that); + self.$overlay = new Ox.Element() + .addClass('OxOverlay') + .css({ + bottom: '40px' + }) + .append( + $('
').css({ + width: self.overlayWidths[0] + 'px' + }) + ) + .append( + $('
').css({ + left: self.overlayWidths[0] + 'px', + width: self.overlayWidths[1] + 'px' + }) + ) + .append( + $('
').css({ + left: (self.overlayWidths[0] + self.overlayWidths[1]) + 'px', + width: self.overlayWidths[2] + 'px' + }) + ) + .bindEvent({ + dragstart: dragstartScrollbar, + drag: dragScrollbar, + dragpause: dragpauseScrollbar, + dragend: dragendScrollbar + }) + .appendTo(that); + + self.$zoombar = new Ox.Element() + .css({ + position: 'absolute', + bottom: 24 + 'px', + height: '16px' + }) + .appendTo(that); + self.$zoomInput = new Ox.Range({ + arrows: true, + max: self.maxZoom, + min: 0, + size: self.options.width, + thumbSize: 32, + thumbValue: true, + value: self.options.zoom + }) + .bindEvent({ + change: changeZoom + }) + .appendTo(self.$zoombar); + + self.$statusbar = new Ox.Bar({ + size: 24 + }) + .css({ + // fixme: no need to set position absolute with map statusbar + position: 'absolute', + bottom: 0, + textAlign: 'center' + }) + .appendTo(that); + + self.$tooltip = new Ox.Tooltip({ + animate: false + }) + .css({ + textAlign: 'center' + }); + + renderCalendar(); + + function changeDate() { + + } + + function changeZoom(event, data) { + self.options.zoom = data.value; + renderCalendar(); + } + + function doubleclick(event, e) { + if ($(e.target).is(':not(.OxLine > .OxDate)')) { + if (self.options.zoom < self.maxZoom) { + self.options.date = new Date( + (+self.options.date + +getMouseDate(e)) / 2 + ); + self.options.zoom++; + } + renderCalendar(); + } + } + + function dragstart(event, e) { + if ($(e.target).is(':not(.OxLine > .OxDate)')) { + self.drag = {x: e.clientX}; + } + } + + function drag(event, e) { + if (self.drag) { + ///* + self.$content.css({ + marginLeft: (e.clientX - self.drag.x) + 'px' + }); + self.$scrollbar.css({ + marginLeft: Math.round((e.clientX - self.drag.x) / 16) + 'px' + }); + //*/ + /* + self.options.date = new Date( + +self.options.date - e.clientDX * getSecondsPerPixel() * 1000 + ); + */ + //self.drag = {x: e.clientX}; + } + } + + function dragpause(event, e) { + if (self.drag) { + dragafter(e); + self.drag = {x: e.clientX}; + } + } + + function dragend(event, e) { + if (self.drag) { + dragafter(e); + self.drag = null; + } + } + + function dragafter(e) { + self.options.date = new Date( + +self.options.date - (e.clientX - self.drag.x) * getSecondsPerPixel() * 1000 + ); + self.$content.css({ + marginLeft: 0 + }); + self.$scrollbar.css({ + marginLeft: 0 + }); + renderCalendar(); + } + + function dragstartScrollbar(event, e) { + self.drag = {x: e.clientX}; + } + + function dragScrollbar(event, e) { + self.$content.css({ + marginLeft: ((e.clientX - self.drag.x) * 16) + 'px' + }); + self.$scrollbar.css({ + marginLeft: (e.clientX - self.drag.x) + 'px' + }); + } + + function dragpauseScrollbar(event, e) { + dragafterScrollbar(e); + self.drag = {x: e.clientX}; + } + + function dragendScrollbar(event, e) { + dragafterScrollbar(e); + self.drag = null; + } + + function dragafterScrollbar(e) { + self.options.date = new Date( + +self.options.date + (self.drag.x - e.clientX) * getSecondsPerPixel() * 1000 * 16 + ); + // fixme: duplicated + self.$content.css({ + marginLeft: 0 + }); + self.$scrollbar.css({ + marginLeft: 0 + }); + renderCalendar(); + } + + function formatDate(date) { + var isFullDays = Ox.formatDate(date.start, '%H:%M:%S') == '00:00:00' && + Ox.formatDate(date.stop, '%H:%M:%S') == '00:00:00', + isOneDay = isFullDays && date.stop - date.start == 86400000, // fixme: wrong, DST + isSameDay = Ox.formatDate(date.start, '%Y-%m-%d') == + Ox.formatDate(date.stop, '%Y-%m-%d'), + isSameYear = date.start.getFullYear() == date.stop.getFullYear(), + timeFormat = isFullDays ? '' : ', %H:%M:%S', + str = Ox.formatDate(date.start, '%a, %b %e'); + if (isOneDay || isSameDay || !isSameYear) { + str += Ox.formatDate(date.start, ', %Y' + timeFormat); + } + if (!isOneDay && !isSameDay) { + str += Ox.formatDate(date.stop, ' - %a, %b %e, %Y' + timeFormat); + } + if (isSameDay) { + str += Ox.formatDate(date.stop, ' - ' + timeFormat.replace(', ', '')); + } + return str; + } + + function getCalendarDate() { + var ms = self.options.width * getSecondsPerPixel() * 1000; + return { + start: new Date(+self.options.date - ms / 2), + stop: new Date(+self.options.date + ms / 2) + }; + } + + function getDateByName(name) { + var date = {}; + Ox.forEach(self.options.dates, function(v) { + if (v.name == name) { + date = v; + return false; + } + }); + return date; + } + + function getDateElement(date, zoom) { + var left = getPosition(date.start, zoom), + width = Math.max(getPosition(date.stop, zoom) - left, 1); + return new Ox.Element() + .addClass('OxDate') + .css({ + left: left + 'px', + width: width + 'px' + }) + .data({ + name: date.name + }) + .html(' ' + date.name); + } + + function getMouseDate(e) { + return new Date(+self.options.date + ( + e.clientX - that.offset().left - self.options.width / 2 - 1 + ) * getSecondsPerPixel() * 1000); + } + + function getPixelsPerSecond(zoom) { + return Math.pow(2, (zoom || self.options.zoom) - (self.maxZoom - 4)); + } + + function getPosition(date, zoom) { + return Math.round( + self.options.width / 2 + + (date - self.options.date) / 1000 * + getPixelsPerSecond(zoom || self.options.zoom) + ); + } + + function getSecondsPerPixel(zoom) { + return 1 / getPixelsPerSecond(zoom); + } + + function getBackgroundElements(zoom) { + // fixme: duplicated + var $elements = [], + units = getUnits(zoom), + n, value, width; + [1, 0].forEach(function(u) { + var unit = units[u], + value = unit.value(self.options.date), + width = unit.seconds * getPixelsPerSecond(zoom), + n = Math.ceil(self.options.width * 1.5/* * 16*/ / width); + Ox.loop(-n, n + 1, function(i) { + $elements.push( + new Ox.Element() + .addClass( + u == 0 ? 'line' : Ox.mod(value + i, 2) == 0 ? 'even' : 'odd' + ) + .css({ + left: getPosition(new Date(unit.date(value + i)), zoom) + 'px', + width: (u == 0 ? 1 : width) + 'px' + }) + ); + }); + }); + return $elements; + } + + function getTimelineElements(zoom) { + var $elements = [], + unit = getUnits(zoom)[0], + value = unit.value(self.options.date), + width = unit.seconds * getPixelsPerSecond(zoom); + n = Math.ceil(self.options.width * 1.5/* * 16*/ / width); + Ox.loop(-n, n + 1, function(i) { + $elements.push( + getDateElement({ + name: unit.name(value + i), + start: new Date(unit.date(value + i)), + stop: new Date(unit.date(value + i + 1)) + }, zoom) + .addClass(Ox.mod(value + i, 2) == 0 ? 'even' : 'odd') + ); + }); + return $elements; + } + + function getUnits(zoom) { + // returns array of 2 units + // units[0] for timeline + // units[1] for background + var pixelsPerSecond = getPixelsPerSecond(zoom), + units; + self.units = self.units.reverse(); + Ox.forEach(self.units, function(v, i) { + width = Math.round(v.seconds * pixelsPerSecond); + if (width >= self.minLabelWidth) { + units = [self.units[i], self.units[i - 1]]; + return false; + } + }); + self.units = self.units.reverse(); + return units; + } + + function mouseleave() { + self.$tooltip.hide(); + } + + function mousemove(e) { + var $target = $(e.target), + date, title; + if ($target.is('.OxLine > .OxDate')) { + date = getDateByName($target.data('name')); + title = '' + date.name + '
' + + formatDate(date); + } else { + title = Ox.formatDate(getMouseDate(e), '%a, %b %e, %Y, %H:%M:%S'); + } + self.$tooltip.options({ + title: title + }) + .show(e.clientX, e.clientY); + } + + function mousewheel(e, delta, deltaX, deltaY) { + //Ox.print('mousewheel', delta, deltaX, deltaY); + var deltaZ = 0; + if (!self.mousewheel && deltaY && Math.abs(deltaY) > Math.abs(deltaX)) { + if (deltaY < 0 && self.options.zoom > 0) { + deltaZ = -1 + } else if (deltaY > 0 && self.options.zoom < self.maxZoom) { + deltaZ = 1 + } + if (deltaZ) { + self.options.date = deltaZ == -1 ? + new Date(2 * +self.options.date - +getMouseDate(e)) : + new Date((+self.options.date + +getMouseDate(e)) / 2) + self.options.zoom += deltaZ; + self.$zoomInput.options({value: self.options.zoom}); + renderCalendar(); + } + } + self.mousewheel = true; + setTimeout(function() { + self.mousewheel = false; + }, 250); + } + + function overlaps(date0, date1) { + return ( + date0.start >= date1.start && date0.start < date1.stop + ) || ( + date1.start >= date0.start && date1.start < date0.stop + ); + } + + function renderCalendar() { + $('.OxBackground').empty(); + $('.OxDate').remove(); + renderBackground(); + renderTimelines(); + renderDates(); + self.$statusbar.html( + Ox.formatDate(self.options.date, '%Y-%m-%d %H:%M:%S %s') + ); + } + + function renderBackground() { + getBackgroundElements(self.options.zoom).forEach(function($element) { + $element.appendTo(self.$background); + }); + } + + function renderDates() { + var calendarDate = getCalendarDate(); + lineDates = []; + self.options.dates.filter(function(date) { + // filter out dates outside the visible area + return overlaps(date, calendarDate); + }).sort(function(a, b) { + // sort dates by duration, descending + return (b.stop - b.start) - (a.stop - a.start); + }).forEach(function(date, i) { + var line = lineDates.length; + // traverse lines + Ox.forEach(lineDates, function(dates, line_) { + var fits = true; + // traverse dates in line + Ox.forEach(dates, function(date_) { + // if overlaps, check next line + if (overlaps(date, date_)) { + fits = false; + return false; + } + }); + if (fits) { + line = line_; + return false; + } + }); + if (line == lineDates.length) { + lineDates[line] = []; + } + lineDates[line].push(date); + }); + $('.OxLine').remove(); + lineDates.forEach(function(dates, line) { + var $line = new Ox.Element() + .addClass('OxLine') + .css({ + top: ((line + 1) * 16) + 'px' + }) + .appendTo(self.$content); + dates.sort(function(a, b) { + // sort dates by start, ascending + return a.start - b.start; + }).forEach(function(date) { + getDateElement(date).appendTo($line); + }); + }); + } + + function renderTimelines() { + Ox.print(self.options.zoom, Math.max(self.options.zoom - 4, 0)) + getTimelineElements(self.options.zoom).forEach(function($element) { + $element.appendTo(self.$scalebar.$element); + }); + getTimelineElements(Math.max(self.options.zoom - 4, 0)).forEach(function($element) { + $element.appendTo(self.$scrollbar.$element); + }); + } + + function singleclick(event, e) { + if ($(e.target).is(':not(.OxLine > .OxDate)')) { + self.options.date = getMouseDate(e); + renderCalendar(); + } + } + + self.onChange = function(key, val) { + if (key == 'date') { + + } else if (key == 'zoom') { + + } + }; + + return that; + +}; diff --git a/source/js/Ox.CalendarDate.js b/source/js/Ox.CalendarDate.js new file mode 100644 index 00000000..cb37a9d1 --- /dev/null +++ b/source/js/Ox.CalendarDate.js @@ -0,0 +1,25 @@ +Ox.CalendarDate = function(options) { + + var self = {}, + that = this; + + ['start', 'stop'].forEach(function(v) { + var date = self.options[v]; + if (Ox.isString(date)) { + date = new Date(self.options[v]); + } + }); + + self.duration = self.options.stop - self.options.start; + + that.format = function() { + + }; + + that.formatDuration = function() { + + }; + + return that; + +}; diff --git a/source/js/Ox.Checkbox.js b/source/js/Ox.Checkbox.js new file mode 100644 index 00000000..088d026c --- /dev/null +++ b/source/js/Ox.Checkbox.js @@ -0,0 +1,104 @@ +Ox.Checkbox = function(options, self) { + + /** + options + disabled boolean, if true, checkbox is disabled + id element id + group boolean, if true, checkbox is part of a group + checked boolean, if true, checkbox is checked + title string, text on label + width integer, width in px + methods: + toggleChecked function() + toggles checked property + returns that + events: + change triggered when checked property changes + passes {checked, id, title} + */ + + var self = self || {}, + that = new Ox.Element('div', self) + .defaults({ + disabled: false, + id: '', + group: false, + checked: false, + overlap: 'none', + title: '', + width: 'auto' + }) + .options(options || {}) + .addClass('OxCheckbox' + + (self.options.overlap == 'none' ? '' : ' OxOverlap' + + Ox.toTitleCase(self.options.overlap)) + ) + .attr(self.options.disabled ? { + disabled: 'disabled' + } : {}); + + if (self.options.title) { + self.options.width != 'auto' && that.css({ + width: self.options.width + 'px' + }); + self.$title = new Ox.Label({ + disabled: self.options.disabled, + id: self.options.id + 'Label', + overlap: 'left', + title: self.options.title, + width: self.options.width - 16 + }) + .css({ + float: 'right' + }) + .click(clickTitle) + .appendTo(that); + } + + self.$button = new Ox.Button({ + disabled: self.options.disabled, + id: self.options.id + 'Button', + title: [ + {id: 'none', title: 'none', selected: !self.options.checked}, + {id: 'check', title: 'check', selected: self.options.checked} + ], + type: 'image' + }) + .addClass('OxCheckbox') + .click(clickButton) + .appendTo(that); + + function clickButton() { + self.options.checked = !self.options.checked; + // click will have toggled the button, + // if it is part of a group, we have to revert that + self.options.group && that.toggleChecked(); + that.triggerEvent('change', { + checked: self.options.checked, + id: self.options.id, + title: self.options.title + }); + } + + function clickTitle() { + !self.options.disabled && self.$button.trigger('click'); + } + + self.onChange = function(key, value) { + if (key == 'checked') { + that.toggleChecked(); + } + }; + + that.checked = function() { + return self.options.checked; + } + + that.toggleChecked = function() { + self.$button.toggleTitle(); + return that; + } + + return that; + +}; diff --git a/source/js/Ox.CheckboxGroup.js b/source/js/Ox.CheckboxGroup.js new file mode 100644 index 00000000..236f7969 --- /dev/null +++ b/source/js/Ox.CheckboxGroup.js @@ -0,0 +1,70 @@ +Ox.CheckboxGroup = function(options, self) { + + /** + options + checkboxes [] array of checkboxes + max 1 integer + min 1 integer + width integer, width in px + events: + change triggered when checked property changes + passes {checked, id, title} + */ + + var self = self || {}, + that = new Ox.Element('div', self) + .defaults({ + checkboxes: [], + max: 1, + min: 1, + width: 256 + }) + .options(options || {}) + .addClass('OxCheckboxGroup'); + + self.optionGroup = new Ox.OptionGroup( + self.options.checkboxes, + self.options.min, + self.options.max); + self.options.checkboxes = self.optionGroup.init(); + + $.extend(self, { + $checkboxes: [], + checkboxWidth: $.map(Ox.divideInt( + self.options.width + (self.options.checkboxes.length - 1) * 6, + self.options.checkboxes.length + ), function(v, i) { + return v + (i < self.options.checkboxes.length - 1 ? 10 : 0); + }) + }); + self.options.checkboxes.forEach(function(checkbox, position) { + var id = self.options.id + Ox.toTitleCase(checkbox.id) + self.$checkboxes[position] = new Ox.Checkbox($.extend(checkbox, { + group: true, + id: id, + width: self.checkboxWidth[position] + })) + .bindEvent('change', function() { + change(position); + }) + .appendTo(that); + }); + + function change(pos) { + var toggled = self.optionGroup.toggle(pos); + //Ox.print('change', pos, 'toggled', toggled) + if (toggled.length) { + toggled.forEach(function(pos, i) { + self.$checkboxes[pos].toggleChecked(); + }); + that.triggerEvent('change', { + checked: $.map(self.optionGroup.checked(), function(v, i) { + return self.options.checkboxes[v].id; + }) + }); + } + } + + return that; + +}; diff --git a/source/js/Ox.Clipboard.js b/source/js/Ox.Clipboard.js new file mode 100644 index 00000000..352113b1 --- /dev/null +++ b/source/js/Ox.Clipboard.js @@ -0,0 +1,22 @@ +Ox.Clipboard = function() { + /*** + Ox.Clipboard + Basic clipboard handler + Methods + copy(data) copy data to clipboard + paste paste data from clipboard + ***/ + var clipboard = {}; + return { + _print: function() { + Ox.print(JSON.stringify(clipboard)); + }, + copy: function(data) { + clipboard = data; + Ox.print('copy', JSON.stringify(clipboard)); + }, + paste: function(type) { + return clipboard; + } + }; +}(); diff --git a/source/js/Ox.CollapsePanel.js b/source/js/Ox.CollapsePanel.js new file mode 100644 index 00000000..adf8beb8 --- /dev/null +++ b/source/js/Ox.CollapsePanel.js @@ -0,0 +1,83 @@ +/** +*/ +Ox.CollapsePanel = function(options, self) { + var self = self || {}, + that = new Ox.Panel({}, self) + .defaults({ + collapsed: false, + extras: [], + size: 16, + title: '' + }) + .options(options) + .addClass('OxCollapsePanel'), + // fixme: the following should all be self.foo + title = self.options.collapsed ? + [{id: 'expand', title: 'right'}, {id: 'collapse', title: 'down'}] : + [{id: 'collapse', title: 'down'}, {id: 'expand', title: 'right'}], + $titlebar = new Ox.Bar({ + orientation: 'horizontal', + size: self.options.size, + }) + .dblclick(dblclickTitlebar) + .appendTo(that), + $switch = new Ox.Button({ + style: 'symbol', + title: title, + type: 'image', + }) + .click(toggleCollapsed) + .appendTo($titlebar), + $title = new Ox.Element() + .addClass('OxTitle') + .html(self.options.title/*.toUpperCase()*/) + .appendTo($titlebar), + $extras; + if (self.options.extras.length) { + $extras = new Ox.Element() + .addClass('OxExtras') + .appendTo($titlebar); + self.options.extras.forEach(function($extra) { + $extra.appendTo($extras); + }); + } + that.$content = new Ox.Element() + .addClass('OxContent') + .appendTo(that); + // fixme: doesn't work, content still empty + // need to hide it if collapsed + if (self.options.collapsed) { + that.$content.css({ + marginTop: -that.$content.height() + 'px' + }); + } + function dblclickTitlebar(e) { + if (!$(e.target).hasClass('OxButton')) { + $switch.trigger('click'); + } + } + function toggleCollapsed() { + var marginTop; + self.options.collapsed = !self.options.collapsed; + marginTop = self.options.collapsed ? -that.$content.height() : 0; + that.$content.animate({ + marginTop: marginTop + 'px' + }, 200); + that.triggerEvent('toggle', { + collapsed: self.options.collapsed + }); + } + self.onChange = function(key, value) { + if (key == 'collapsed') { + + } else if (key == 'title') { + $title.html(self.options.title); + } + }; + that.update = function() { // fixme: used anywhere? + self.options.collapsed && that.$content.css({ + marginTop: -that.$content.height() + }); + }; + return that; +}; diff --git a/source/js/Ox.ColorInput.js b/source/js/Ox.ColorInput.js new file mode 100644 index 00000000..179a0e64 --- /dev/null +++ b/source/js/Ox.ColorInput.js @@ -0,0 +1,71 @@ +Ox.ColorInput = function(options, self) { + + var self = $.extend(self || {}, { + options: $.extend({ + id: '', + value: '0, 0, 0' + }, options) + }), + that; + + self.values = self.options.value.split(', '); + self.$inputs = []; + ['red', 'green', 'blue'].forEach(function(v, i) { + self.$inputs[i] = new Ox.Input({ + id: v, + max: 255, + type: 'integer', + value: self.values[i], + width: 36 + }) + .bindEvent('autovalidate', change); + }); + self.$inputs[3] = new Ox.Label({ + id: 'color', + width: 36 + }) + .css({ + background: 'rgb(' + self.options.value + ')' + }); + self.$inputs[4] = new Ox.ColorPicker({ + id: 'picker' + }) + .bindEvent('change', function(event, data) { + //Ox.print('change function called'); + self.options.value = data.value; + self.values = data.value.split(', '); + Ox.range(3).forEach(function(i) { + self.$inputs[i].options({ + value: self.values[i] + }); + }); + }) + .options({ + width: 16 // this is just a hack to make the InputGroup layout work + }); + + that = new Ox.InputGroup({ + id: self.options.id, + inputs: self.$inputs, + separators: [ + {title: ',', width: 8}, + {title: ',', width: 8}, + {title: '', width: 8}, + {title: '', width: 8} + ], + value: self.options.value // fixme: it'd be nicer if this would be taken care of by passing self + }, self) + .bindEvent('change', change); + + function change() { + self.options.value = $.map(self.$inputs, function(v, i) { + return v.options('value'); + }).join(', '); + self.$inputs[3].css({ + background: 'rgb(' + self.options.value + ')' + }); + } + + return that; + +}; diff --git a/source/js/Ox.ColorPicker.js b/source/js/Ox.ColorPicker.js new file mode 100644 index 00000000..2b37769e --- /dev/null +++ b/source/js/Ox.ColorPicker.js @@ -0,0 +1,91 @@ +Ox.ColorPicker = function(options, self) { + + var self = self || {}, + that = new Ox.Element('div', self) + .defaults({ + id: '', + value: '0, 0, 0' + }) + .options(options || {}); + + //Ox.print(self) + self.$ranges = []; + self.rgb = ['red', 'green', 'blue']; + self.values = self.options.value.split(', '); + + Ox.range(3).forEach(function(i) { + self.$ranges[i] = new Ox.Range({ + arrows: true, + id: self.options.id + Ox.toTitleCase(self.rgb[i]), + max: 255, + size: 328, // 256 + 16 + 40 + 16 + thumbSize: 40, + thumbValue: true, + trackColors: getColors(i), + value: self.values[i] + }) + .css({ + position: 'absolute', + top: (i * 15) + 'px' + }) + .bindEvent('change', function(event, data) { + change(i, data.value); + }) + .appendTo(that); + // fixme: make self.$ranges[i].children() work + if (i == 0) { + self.$ranges[i].$element.children('input.OxOverlapRight').css({ + MozBorderRadius: 0, + WebkitBorderRadius: 0 + }); + self.$ranges[i].$element.children('input.OxOverlapLeft').css({ + MozBorderRadius: '0 8px 0 0', + WebkitBorderRadius: '0 8px 0 0' + }); + } else { + self.$ranges[i].$element.children('input').css({ + MozBorderRadius: 0, + WebkitBorderRadius: 0 + }); + } + }); + + that = new Ox.Picker({ + element: that, + elementHeight: 46, + elementWidth: 328, + id: self.options.id + }); + + function change(index, value) { + self.values[index] = value; + self.options.value = self.values.join(', '); + that.$label.css({ + background: 'rgb(' + self.options.value + ')' + }); + Ox.range(3).forEach(function(i) { + if (i != index) { + self.$ranges[i].options({ + trackColors: getColors(i) + }); + } + }); + that.triggerEvent('change', { + value: self.options.value + }); + } + + function getColors(index) { + return [ + 'rgb(' + $.map(Ox.range(3), function(v) { + return v == index ? 0 : self.values[v]; + }).join(', ') + ')', + 'rgb(' + $.map(Ox.range(3), function(v) { + return v == index ? 255 : self.values[v]; + }).join(', ') + ')' + ] + } + + return that; + +}; diff --git a/source/js/Ox.Container.js b/source/js/Ox.Container.js new file mode 100644 index 00000000..423e055f --- /dev/null +++ b/source/js/Ox.Container.js @@ -0,0 +1,16 @@ +// fixme: wouldn't it be better to let the elements be, +// rather then $element, $content, and potentially others, +// 0, 1, 2, etc, so that append would append 0, and appendTo +// would append (length - 1)? + +Ox.Container = function(options, self) { + // fixme: to be deprecated + var that = new Ox.Element('div', self) + .options(options || {}) + .addClass('OxContainer'); + that.$content = new Ox.Element('div', self) + .options(options || {}) + .addClass('OxContent') + .appendTo(that); + return that; +}; diff --git a/source/js/Ox.DateInput.js b/source/js/Ox.DateInput.js new file mode 100644 index 00000000..59b189cd --- /dev/null +++ b/source/js/Ox.DateInput.js @@ -0,0 +1,217 @@ +Ox.DateInput = function(options, self) { + + /** + options: + format: 'short' + value: date value + weekday: false + width: { + day: 32, + month: options.format == 'long' ? 80 : (options.format == 'medium' ? 40 : 32), + weekday: options.format == 'long' ? 80 : 40, + year: 48 + } + */ + + var self = $.extend(self || {}, { + options: $.extend({ + format: 'short', + value: Ox.formatDate(new Date(), '%F'), + weekday: false, + width: { + day: 32, + month: options.format == 'long' ? 80 : (options.format == 'medium' ? 40 : 32), + weekday: options.format == 'long' ? 80 : 40, + year: 48 + } + }, options) + }), + that; + + $.extend(self, { + date: new Date(self.options.value.replace(/-/g, '/')), + formats: { + day: '%d', + month: self.options.format == 'short' ? '%m' : + (self.options.format == 'medium' ? '%b' : '%B'), + weekday: self.options.format == 'long' ? '%A' : '%a', + year: '%Y' + }, + months: self.options.format == 'long' ? Ox.MONTHS : $.map(Ox.MONTHS, function(v, i) { + return v.substr(0, 3); + }), + weekdays: self.options.format == 'long' ? Ox.WEEKDAYS : $.map(Ox.WEEKDAYS, function(v, i) { + return v.substr(0, 3); + }) + }); + + self.$input = $.extend(self.options.weekday ? { + weekday: new Ox.Input({ + autocomplete: self.weekdays, + autocompleteReplace: true, + autocompleteReplaceCorrect: true, + id: 'weekday', + value: Ox.formatDate(self.date, self.formats.weekday), + width: self.options.width.weekday + }) + .bindEvent('autocomplete', changeWeekday), + } : {}, { + day: new Ox.Input({ + autocomplete: $.map(Ox.range(1, Ox.getDaysInMonth( + parseInt(Ox.formatDate(self.date, '%Y'), 10), + parseInt(Ox.formatDate(self.date, '%m'), 10) + ) + 1), function(v, i) { + return self.options.format == 'short' ? Ox.pad(v, 2) : v.toString(); + }), + autocompleteReplace: true, + autocompleteReplaceCorrect: true, + id: 'day', + value: Ox.formatDate(self.date, self.formats.day), + textAlign: 'right', + width: self.options.width.day + }) + .bindEvent('autocomplete', changeDay), + month: new Ox.Input({ + autocomplete: self.options.format == 'short' ? $.map(Ox.range(1, 13), function(v, i) { + return Ox.pad(v, 2); + }) : self.months, + autocompleteReplace: true, + autocompleteReplaceCorrect: true, + id: 'month', + value: Ox.formatDate(self.date, self.formats.month), + textAlign: self.options.format == 'short' ? 'right' : 'left', + width: self.options.width.month + }) + .bindEvent('autocomplete', changeMonthOrYear), + year: new Ox.Input({ + autocomplete: $.map($.merge(Ox.range(1900, 3000), Ox.range(1000, 1900)), function(v, i) { + return v.toString(); + }), + autocompleteReplace: true, + autocompleteReplaceCorrect: true, + id: 'year', + value: Ox.formatDate(self.date, self.formats.year), + textAlign: 'right', + width: self.options.width.year + }) + .bindEvent('autocomplete', changeMonthOrYear) + }); + + that = new Ox.InputGroup($.extend(self.options, { + id: self.options.id, + inputs: $.merge(self.options.weekday ? [ + self.$input.weekday + ] : [], self.options.format == 'short' ? [ + self.$input.year, self.$input.month, self.$input.day + ] : [ + self.$input.month, self.$input.day, self.$input.year + ]), + separators: $.merge(self.options.weekday ? [ + {title: self.options.format == 'short' ? '' : ',', width: 8}, + ] : [], self.options.format == 'short' ? [ + {title: '-', width: 8}, {title: '-', width: 8} + ] : [ + {title: '', width: 8}, {title: ',', width: 8} + ]), + width: 0 + }), self); + + //Ox.print('SELF', self) + + function changeDay() { + self.options.weekday && self.$input.weekday.options({ + value: Ox.formatDate(new Date([ + self.$input.month.options('value'), + self.$input.day.options('value'), + self.$input.year.options('value') + ].join(' ')), self.formats.weekday) + }); + setValue(); + } + + function changeMonthOrYear() { + var day = self.$input.day.options('value'), + month = self.$input.month.options('value'), + year = self.$input.year.options('value'), + days = Ox.getDaysInMonth(year, self.options.format == 'short' ? parseInt(month, 10) : month); + day = day <= days ? day : days; + //Ox.print(year, month, 'day days', day, days) + self.options.weekday && self.$input.weekday.options({ + value: Ox.formatDate(new Date([month, day, year].join(' ')), self.formats.weekday) + }); + self.$input.day.options({ + autocomplete: $.map(Ox.range(1, days + 1), function(v, i) { + return self.options.format == 'short' ? Ox.pad(v, 2) : v.toString(); + }), + value: self.options.format == 'short' ? Ox.pad(day, 2) : day.toString() + }); + setValue(); + } + + function changeWeekday() { + var date = getDateInWeek( + self.$input.weekday.options('value'), + self.$input.month.options('value'), + self.$input.day.options('value'), + self.$input.year.options('value') + ); + self.$input.month.options({value: date.month}); + self.$input.day.options({ + autocomplete: $.map(Ox.range(1, Ox.getDaysInMonth(date.year, date.month) + 1), function(v, i) { + return self.options.format == 'short' ? Ox.pad(v, 2) : v.toString(); + }), + value: date.day + }); + self.$input.year.options({value: date.year}); + setValue(); + } + + function getDateInWeek(weekday, month, day, year) { + //Ox.print([month, day, year].join(' ')) + var date = new Date([month, day, year].join(' ')); + date = Ox.getDateInWeek(date, weekday); + return { + day: Ox.formatDate(date, self.formats.day), + month: Ox.formatDate(date, self.formats.month), + year: Ox.formatDate(date, self.formats.year) + }; + } + + function setValue() { + self.options.value = Ox.formatDate(new Date(self.options.format == 'short' ? [ + self.$input.year.options('value'), + self.$input.month.options('value'), + self.$input.day.options('value') + ].join('/') : [ + self.$input.month.options('value'), + self.$input.day.options('value'), + self.$input.year.options('value') + ].join(' ')), '%F'); + } + + /* + function normalize() { + var year = that.getInputById('year').options('value'), + month = that.getInputById('month').options('value'), + day = that.getInputById('day').options('value') + return { + year: year, + month: self.options.format == 'short' ? month : + Ox.pad((format == 'medium' ? Ox.WEEKDAYS.map(function(v, i) { + return v.substr(0, 3); + }) : Ox.WEEKDAYS).indexOf(month), 2), + day: Ox.pad(day, 2) + } + } + */ + + /* + that.serialize = function() { + var normal = normalize(); + return [normal.year, normal.month, normal.day].join('-'); + } + */ + + return that; + +}; diff --git a/source/js/Ox.DateTimeInput.js b/source/js/Ox.DateTimeInput.js new file mode 100644 index 00000000..33d0f9ac --- /dev/null +++ b/source/js/Ox.DateTimeInput.js @@ -0,0 +1,48 @@ +Ox.DateTimeInput = function(options, self) { + + var self = self || {}, + that = new Ox.Element({}, self) + .defaults({ + ampm: false, + format: 'short', + seconds: false, + value: Ox.formatDate(new Date(), '%F %T'), + weekday: false + }) + .options(options || {}); + + self.values = self.options.value.split(' '); + //Ox.print(self.values) + + that = new Ox.InputGroup({ + inputs: [ + new Ox.DateInput({ + format: self.options.format, + id: 'date', + value: self.values[0], + weekday: self.options.weekday + }), + new Ox.TimeInput({ + ampm: self.options.ampm, + id: 'time', + value: self.values[1], + seconds: self.options.seconds + }) + ], + separators: [ + {title: '', width: 8} + ], + value: self.options.value + }) + .bindEvent('change', setValue); + + function setValue() { + self.options.value = [ + self.options('inputs')[0].options('value'), + self.options('inputs')[1].options('value') + ].join(' '); + } + + return that; + +}; diff --git a/source/js/Ox.Dialog.js b/source/js/Ox.Dialog.js new file mode 100644 index 00000000..507ee0a2 --- /dev/null +++ b/source/js/Ox.Dialog.js @@ -0,0 +1,387 @@ +Ox.Dialog = function(options, self) { + + // fixme: dialog should be derived from a generic draggable + // fixme: buttons should have a close attribute, or the dialog a close id + var self = self || {}, + that = new Ox.Element('div', self) + .defaults({ + title: '', + buttons: [], + content: null, + height: 216, + keys: {}, + minHeight: 144, + minWidth: 256, + movable: true, + padding: 16, + resizable: true, + width: 384 + }) + .options(options || {}) + .addClass('OxDialog') + .bindEvent({ + key_enter: function() { + keypress('enter'); + }, + key_escape: function() { + //Ox.print('KEY ESCAPE') + keypress('escape'); + } + }); + + $.extend(self, { + initialWidth: self.options.width, + initialHeight: self.options.height + }) + + that.$titlebar = new Ox.Bar({ + size: 'medium' + }) + .addClass('OxTitleBar') + .appendTo(that); + self.options.movable && that.$titlebar + .dblclick(center) + .bindEvent({ + dragstart: dragstart, + drag: drag + }); + + that.$title = new Ox.Element() + .addClass('OxTitle') + .html(self.options.title) + .appendTo(that.$titlebar); + + that.$content = new Ox.Element() + .addClass('OxContent') + .css({ + padding: self.options.padding + 'px', + overflow: 'auto' + }) + .append(self.options.content) + .appendTo(that); + + that.$buttonsbar = new Ox.Bar({}) + .addClass('OxButtonsBar') + .appendTo(that); + loadButtons(); + + //that.$buttons[0].focus(); + + that.$layer = new Ox.Element() // fixme: Layer widget that would handle click? + .addClass('OxLayer') + .mousedown(mousedownLayer) + .mouseup(mouseupLayer); + + function center() { + var documentHeight = Ox.UI.$document.height(); + that.css({ + left: 0, + top: Math.max(parseInt(-documentHeight / 10), self.options.height - documentHeight + 40) + 'px', + right: 0, + bottom: 0, + margin: 'auto' + }); + } + + function dragstart(event, e) { + self.drag = { + bodyWidth: Ox.UI.$body.width(), + bodyHeight: Ox.UI.$document.height(), + elementWidth: that.width(), + offset: that.offset(), + x: e.clientX, + y: e.clientY + }; + that.css({ + margin: 0 + }); + } + + function drag(event, e) { + var left = Ox.limit( + self.drag.offset.left - self.drag.x + e.clientX, + 24 - self.drag.elementWidth, self.drag.bodyWidth - 24 + //0, self.drag.documentWidth - self.drag.elementWidth + ), + top = Ox.limit( + self.drag.offset.top - self.drag.y + e.clientY, + 24, self.drag.bodyHeight - 24 + //24, self.drag.documentHeight - self.drag.elementHeight + ); + that.css({ + left: left + 'px', + top: top + 'px' + }); + } + + function dragstartResize(event, e) { + self.drag = { + documentWidth: Ox.UI.$document.width(), + documentHeight: Ox.UI.$document.height(), + elementWidth: that.width(), + elementHeight: that.height(), + offset: that.offset(), + x: e.clientX, + y: e.clientY + }; + $.extend(self.drag, { + ratio: self.drag.elementWidth / self.drag.elementHeight + }); + that.css({ + left: self.drag.offset.left, + top: self.drag.offset.top, + margin: 0 + }); + } + + function dragResize(event, e) { + if (!e.shiftKey) { + self.drag.ratio = self.options.width / self.options.height; + } + self.options.width = Ox.limit( + self.drag.elementWidth - self.drag.x + e.clientX, + self.options.minWidth, + Math.min( + self.drag.documentWidth, + self.drag.documentWidth - self.drag.offset.left + ) + ); + self.options.height = Ox.limit( + self.drag.elementHeight - self.drag.y + e.clientY, + self.options.minHeight, + Math.min( + self.drag.documentHeight, + self.drag.documentHeight - self.drag.offset.top + ) + ); + if (e.shiftKey) { + self.options.height = Ox.limit( + self.options.width / self.drag.ratio, + self.options.minHeight, + Math.min( + self.drag.documentHeight, + self.drag.documentHeight - self.drag.offset.top + ) + ); + self.options.width = self.options.height * self.drag.ratio; + } + that.width(self.options.width); + that.height(self.options.height); + that.$content.height(self.options.height - 48 - 2 * self.options.padding); // fixme: this should happen automatically + } + + function dragendResize(event, e) { + triggerResizeEvent(); + } + + function getButtonById(id) { + var ret = null; + //Ox.print('that.$buttons', that.$buttons, id) + Ox.forEach(that.$buttons, function(button) { + if (button.options('id') == id) { + ret = button; + return false; + } + }); + return ret; + } + + function keypress(key) { + var id = self.options.keys[key]; + //Ox.print('X', key, self.options.keys) + id && getButtonById(id).$element.trigger('click'); + } + + function loadButtons() { + /*Ox.print('loadButtons', $.map(self.options.buttons, function(v) { + return v; + }));*/ + if (that.$buttons) { + that.$buttons.forEach(function($button) { + $button.removeElement(); + }); + that.$resize.removeElement(); + // that.$buttonsbar.empty(); + } + that.$buttons = []; + if (!Ox.isArray(self.options.buttons[0])) { + self.options.buttons = [[], self.options.buttons]; + } + self.options.buttons[0].forEach(function(button, i) { + that.$buttons[i] = button + .addClass('OxLeft') + .appendTo(that.$buttonsbar); + }); + if (self.options.resizable) { + that.$resize = new Ox.Element() + .addClass('OxResize') + .dblclick(reset) + .bindEvent({ + dragstart: dragstartResize, + drag: dragResize, + dragend: dragendResize + }) + .appendTo(that.$buttonsbar); + } + self.options.buttons[1].reverse().forEach(function(button) { + that.$buttons[that.$buttons.length] = button + .addClass('OxRight') + .appendTo(that.$buttonsbar); + }); + } + + function mousedownLayer() { + that.$layer.stop().animate({ + opacity: 0.5 + }, 0); + } + + function mouseupLayer() { + that.$layer.stop().animate({ + opacity: 0 + }, 0); + } + + function reset() { + $.extend(self.options, { + height: self.initialHeight, + width: self.initialWidth + }); + that/*.css({ + left: Math.max(that.offset().left, 24 - that.width()) + })*/ + .width(self.options.width) + .height(self.options.height); + that.$content.height(self.options.height - 48 - 2 * self.options.padding); // fixme: this should happen automatically + triggerResizeEvent(); + } + + function triggerResizeEvent() { + that.triggerEvent('resize', { + width: self.options.width, + height: self.options.height + }); + } + + self.onChange = function(key, value) { + if (key == 'buttons') { + loadButtons(); + /* + that.$buttonsbar.children().animate({ + opacity: 0 + }, 100, function() { + loadButtons(); + that.$buttonsbar.children().animate({ + opacity: 1 + }, 100); + }); + */ + } else if (key == 'content') { + that.$content.html(value); + } else if (key == 'height' || key == 'width') { + that.animate({ + height: self.options.height + 'px', + width: self.options.width + 'px' + }, 100); + that.$content.height(self.options.height - 48 - 2 * self.options.padding); // fixme: this should happen automatically + } else if (key == 'title') { + that.$title.animate({ + opacity: 0 + }, 100, function() { + that.$title.html(value).animate({ + opacity: 1 + }, 100); + }); + } + } + + that.center = function() { + + }; + + that.close = function(callback) { + callback = callback || function() {}; + that.animate({ + opacity: 0 + }, 200, function() { + that.$buttons.forEach(function($button) { + $button.removeElement(); + }); + that.loseFocus(); + that.$layer.removeElement(); + that.removeElement(); + callback(); + }); + Ox.UI.$window.unbind('mouseup', mouseupLayer) + return that; + }; + + that.content = function($element) { + that.$content.empty().append($element); + return that; + } + + that.disable = function() { + // to be used on submit of form, like login + that.$layer.addClass('OxFront'); + return that; + }; + + that.disableButton = function(id) { + getButtonById(id).options({ + disabled: true + }); + return that; + }; + + that.enable = function() { + that.$layer.removeClass('OxFront'); + return that; + }; + + that.enableButton = function(id) { + getButtonById(id).options({ + disabled: false + }); + return that; + }; + + that.open = function() { + //Ox.print('before open') + that.$layer.appendTo(Ox.UI.$body); + that.css({ + opacity: 0 + }).appendTo(Ox.UI.$body).animate({ + opacity: 1 + }, 200); + center(); + reset(); + // fixme: the following line prevents preview-style dialog + that.gainFocus(); + Ox.UI.$window.bind('mouseup', mouseupLayer) + //Ox.print('after open') + return that; + }; + + that.size = function(width, height, callback) { + $.extend(self, { + initialWidth: width, + initialHeight: height + }); + $.extend(self.options, { + width: width, + height: height + }); + // fixme: duplicated + that.animate({ + height: self.options.height + 'px', + width: self.options.width + 'px' + }, 100, function() { + that.$content.height(self.options.height - 48 - 2 * self.options.padding); // fixme: this should happen automatically + callback(); + }); + } + + return that; + +}; diff --git a/source/js/Ox.Element.js b/source/js/Ox.Element.js new file mode 100644 index 00000000..4326629b --- /dev/null +++ b/source/js/Ox.Element.js @@ -0,0 +1,324 @@ +// check out http://ejohn.org/apps/learn/#36 (-#38, making fns work w/o new) + +Ox.Element = function() { + + /*** + Basic element object + ***/ + + /* + tooltip option can be any of the following: + string + function(e), returns string + {mousemove: true, title: function(e)} + */ + + return function(options, self) { + + if (!(this instanceof arguments.callee)) { + return new arguments.callee(options, self); + } + + self = self || {}; + self.options = options || {}; + // allow for Ox.Element('tagname', self) + if (typeof self.options == 'string') { + self.options = { + element: self.options + }; + } + if (!self.$eventHandler) { + self.$eventHandler = $('
'); + } + + var that = new Ox.JQueryElement( + $('<' + (self.options.element || 'div') + '>') + ) + .mousedown(mousedown); + + /* + self.options.tooltip && that.bind(Ox.extend({ + mouseenter: mouseenter, + mouseleave: mouseleave + }, self.options.tooltip.mousemove ? { + mousemove: mousemove + } : {})); + */ + + function mousedown(e) { + /* + better mouse events + mousedown: + trigger mousedown + within 250 msec: + mouseup: trigger anyclick ("click" would collide with click events of certain widgets) + mouseup + mousedown: trigger doubleclick + after 250 msec: + mouseup + no mousedown within 250 msec: trigger singleclick + no mouseup within 250 msec: + trigger mouserepeat every 50 msec + trigger dragstart + mousemove: trigger drag + no mousemove within 250 msec: + trigger dragpause + mouseup: trigger dragend + */ + var clientX, clientY, + dragTimeout = 0, + mouseInterval = 0; + if (!self.mouseTimeout) { + // first mousedown + that.triggerEvent('mousedown', e); + self.mouseup = false; + self.mouseTimeout = setTimeout(function() { + self.mouseTimeout = 0; + if (self.mouseup) { + // singleclick + that.triggerEvent('singleclick', e); + } else { + // mouserepeat, drag + clientX = e.clientX; + clientY = e.clientY; + that.triggerEvent('dragstart', e); + mouserepeat(); + mouseInterval = setInterval(mouserepeat, 50); + Ox.UI.$window.unbind('mouseup', mouseup) + .mousemove(mousemove) + .one('mouseup', function(e) { + clearInterval(mouseInterval); + clearTimeout(dragTimeout); + Ox.UI.$window.unbind('mousemove', mousemove); + that.triggerEvent('dragend', extend(e)); + }); + that.one('mouseleave', function() { + clearInterval(mouseInterval); + }); + } + }, 250); + } else { + // second mousedown + clearTimeout(self.mouseTimeout); + self.mouseTimeout = 0; + that.triggerEvent('doubleclick', e); + } + Ox.UI.$window.one('mouseup', mouseup); + function extend(e) { + return Ox.extend({ + clientDX: e.clientX - clientX, + clientDY: e.clientY - clientY + }, e); + } + function mousemove(e) { + e = extend(e); + clearTimeout(dragTimeout); + dragTimeout = setTimeout(function() { + that.triggerEvent('dragpause', e); + }, 250); + that.triggerEvent('drag', e); + } + function mouserepeat() { + that.triggerEvent('mouserepeat'); + } + function mouseup(e) { + // only trigger on firse mouseup + if (!self.mouseup) { + that.triggerEvent('anyclick', e); + self.mouseup = true; + } + } + } + + /* + function mouseenter(e) { + self.$tooltip = new Ox.Tooltip({ + title: Ox.isString(self.options.tooltip) ? + self.options.tooltip : Ox.isFunction(self.options.tooltip) ? + self.options.tooltip(e) : self.options.tooltip.title(e) + }).show(); + } + + function mouseleave(e) { + self.$tooltip.hide(); + } + + function mousemove(e) { + self.$tooltip.options({ + title: self.options.tooltip.title(e) + }); + } + */ + + self.onChange = function() { + // self.onChange(key, value) + // is called when an option changes + // (to be implemented by widget) + // fixme: rename to self.setOption + }; + + that._leakSelf = function() { // fixme: remove + return self; + } + + that.bindEvent = function() { + /*** + binds a function to an event triggered by this object + Usage + bindEvent(event, fn) or bindEvent({event0: fn0, event1: fn1, ...}) + ***/ + if (arguments.length == 1) { + Ox.forEach(arguments[0], function(fn, event) { + // Ox.print(that.id, 'bind', event); + self.$eventHandler.bind('ox_' + event, fn); + }); + } else { + // Ox.print(that.id, 'bind', arguments[0]); + self.$eventHandler.bind('ox_' + arguments[0], arguments[1]); + } + return that; + } + + that.bindEventOnce = function() { + if (arguments.length == 1) { + Ox.forEach(arguments[0], function(fn, event) { + self.$eventHandler.one('ox_' + event, fn); + }); + } else { + self.$eventHandler.one('ox_' + arguments[0], arguments[1]); + } + return that; + }; + + that.defaults = function(defaults) { + /*** + sets the default options + Usage + that.defaults({key0: value0, key1: value1, ...}) + ***/ + self.defaults = defaults; + delete self.options; // fixme: hackish fix for that = Ox.Foo({...}, self).defaults({...}).options({...}) + return that; + }; + + that.gainFocus = function() { + /*** + make this object gain focus + ***/ + Ox.Focus.focus(that.id); + return that; + }; + + that.hasFocus = function() { + /*** + returns true if this object has focus + ***/ + return Ox.Focus.focused() == that.id; + }; + + that.loseFocus = function() { + /*** + make this object lose focus + ***/ + Ox.Focus.blur(that.id); + return that; + }; + + that.options = function() { // fixme: use Ox.getset + /*** + get or set options + Usage + that.options() returns self.options + that.options('foo') returns self.options.foo + that.options('foo', x) sets self.options.foo, + returns that + that.options({foo: x, bar: y}) sets self.options.foo + and self.options.bar, + returns that + ***/ + var args, + length = arguments.length, + oldOptions, + ret; + if (length == 0) { + // options() + ret = self.options; + } else if (length == 1 && typeof arguments[0] == 'string') { + // options(str) + ret = self.options ? self.options[arguments[0]] : options[arguments[0]]; + } else { + // options (str, val) or options({str: val, ...}) + // translate (str, val) to ({str: val}) + args = Ox.makeObject.apply(that, arguments || {}); + oldOptions = $.extend({}, self.options); + // if options have not been set, extend defaults, + // otherwise, extend options + //self.options = $.extend(self.options, self.options ? {} : self.defaults, args); + self.options = $.extend({}, self.defaults, self.options, args); + //self.options = $.extend(self.options || self.defaults, args); + Ox.forEach(args, function(val, key) { + // key == 'id' && id && Ox.Event.changeId(id, value); + /*!Ox.equals(value, oldOptions[key]) &&*/ self.onChange(key, val); + }); + ret = that; + } + return ret; + }; + + that.removeElement = function() { + /*** + remove this element, including its event handler + ***/ + that.loseFocus(); + delete self.$eventHandler; + that.remove(); + delete Ox.UI.elements[that.id]; + return that; + }; + + that.triggerEvent = function() { + /*** + triggers an event + Usage + triggerEvent(event) + triggerEvent(event, data) + triggerEvent({event0: data0, event1: data1, ...}) + ***/ + if (Ox.isObject(arguments[0])) { + Ox.forEach(arguments[0], function(data, event) { + if (['mousedown', 'mouserepeat', 'anyclick', 'singleclick', 'doubleclick', 'dragstart', 'drag', 'dragpause', 'dragend', 'playing'].indexOf(event) == -1) { + Ox.print(that.id, self.options.id, 'trigger', event, data); + } + self.$eventHandler.trigger('ox_' + event, data); + }); + } else { + if (['mousedown', 'mouserepeat', 'anyclick', 'singleclick', 'doubleclick', 'dragstart', 'drag', 'dragpause', 'dragend', 'playing'].indexOf(arguments[0]) == -1) { + Ox.print(that.id, self.options ? self.options.id : '', 'trigger', arguments[0], arguments[1] || {}); + } + self.$eventHandler.trigger('ox_' + arguments[0], arguments[1] || {}); + } + return that; + }; + + that.unbindEvent = function() { + /*** + unbinds a function from an event triggered by this element + Usage + unbindEvent(event, fn) + unbindEvent({event0: fn0, event1: fn1, ...}) + ***/ + if (arguments.length == 1) { + Ox.forEach(arguments[0], function(fn, event) { + // Ox.print(that.id, 'unbind', arguments[0]); + self.$eventHandler.unbind('ox_' + event, fn); + }); + } else { + // Ox.print(that.id, 'unbind', arguments[0]); + self.$eventHandler.unbind('ox_' + arguments[0], arguments[1]); + } + return that; + }; + + return that; + + } + +}(); diff --git a/source/js/Ox.FilesView.js b/source/js/Ox.FilesView.js new file mode 100644 index 00000000..e6d3dfee --- /dev/null +++ b/source/js/Ox.FilesView.js @@ -0,0 +1,182 @@ +// fixme: this is not necessarily part of OxUI + +/* +============================================================================ +Pan.do/ra +============================================================================ +*/ + +Ox.FilesView = function(options, self) { + + var self = self || {}, + that = new Ox.Element('div', self) + .defaults({ + id: '' + }) + .options(options || {}); + + self.$toolbar = new Ox.Bar({ + size: 24 + }); + + self.$orderButton = new Ox.Button({ + title: 'Change Order of Users...' + }) + .css({ + float: 'left', + margin: '4px' + }) + .appendTo(self.$toolbar); + + self.$moveButton = new Ox.Button({ + disabled: 'true', + title: 'Move Selected Files...' + }) + .css({ + float: 'right', + margin: '4px' + }) + .appendTo(self.$toolbar); + + self.$filesList = new Ox.TextList({ + columns: [ + { + align: 'left', + id: 'users', + operator: '+', + title: 'Users', + visible: true, + width: 120 + }, + { + align: 'left', + id: 'folder', + operator: '+', + title: 'Folder', + visible: true, + width: 180 + }, + { + align: 'left', + id: 'name', + operator: '+', + title: 'Name', + visible: true, + width: 360 + }, + { + align: 'left', + id: 'type', + operator: '+', + title: 'Type', + visible: true, + width: 60 + }, + { + align: 'right', + id: 'part', + operator: '+', + title: 'Part', + visible: true, + width: 60 + }, + { + align: 'right', + format: {type: 'value', args: ['B']}, + id: 'size', + operator: '-', + title: 'Size', + visible: true, + width: 90 + }, + { + align: 'right', + format: {type: 'resolution', args: ['px']}, + id: 'resolution', + operator: '-', + title: 'Resolution', + visible: true, + width: 90 + }, + { + align: 'right', + format: {type: 'duration', args: [0, 'short']}, + id: 'duration', + operator: '-', + title: 'Duration', + visible: true, + width: 90 + }, + { + align: 'left', + id: 'oshash', + operator: '+', + title: 'Hash', + unique: true, + visible: false, + width: 120 + }, + { + align: 'left', + id: 'instances', + operator: '+', + title: 'Instances', + visible: false, + width: 120 + } + ], + columnsMovable: true, + columnsRemovable: true, + columnsResizable: true, + columnsVisible: true, + id: 'files', + items: function(data, callback) { + pandora.api.findFiles($.extend(data, { + query: { + conditions: [{ + key: 'id', + value: self.options.id, + operator: '=' + }] + } + }), callback); + }, + scrollbarVisible: true, + sort: [{key: 'name', operator:'+'}] + }) + .bindEvent({ + open: openFiles, + select: selectFiles + }); + + self.$instancesList = new Ox.Element() + .html('No files selected'); + + that.$element = new Ox.SplitPanel({ + elements: [ + { + element: self.$toolbar, + size: 24 + }, + { + element: self.$filesList + }, + { + element: self.$instancesList, + size: 80 + } + ], + orientation: 'vertical' + }); + + function openFiles(event, data) { + //alert(JSON.stringify(self.$filesList.value(data.ids[0], 'instances'))) + } + + function selectFiles(event, data) { + //alert(JSON.stringify(self.$filesList.value(data.ids[0], 'instances'))) + } + + return that; + +}; diff --git a/source/js/Ox.Filter.js b/source/js/Ox.Filter.js new file mode 100644 index 00000000..560b6e82 --- /dev/null +++ b/source/js/Ox.Filter.js @@ -0,0 +1,403 @@ +Ox.Filter = function(options, self) { + + /*** + Options: + Methods: + Events: + ***/ + + var self = self || {}, + that = new Ox.Element('div', self) + .defaults({ + findKeys: [], + query: { + conditions: [], + operator: '&' + }, + sortKeys: [], + viewKeys: [] + }) + .options(options || {}); + + Ox.print('Ox.Filter self.options', self.options) + + $.extend(self, { + conditionOperators: { + date: [ + {id: '', title: 'is'}, + {id: '!', title: 'is not'}, + {id: '<', title: 'is before'}, + {id: '>', title: 'is after'}, + {id: '>&<', title: 'is between'}, + {id: '<|>', title: 'is not between'} + ], + list: [ + {id: '', title: 'is'}, + {id: '!', title: 'is not'} + ], + number: [ + {id: '', title: 'is'}, + {id: '!', title: 'is not'}, + {id: '<', title: 'is less than'}, + {id: '>', title: 'is greater than'}, + {id: '>&<', title: 'is between'}, + {id: '<|>', title: 'is not between'} + ], + string: [ + {id: '=', title: 'is'}, + {id: '!=', title: 'is not'}, + {id: '^', title: 'begins with'}, + {id: '$', title: 'ends with'}, + {id: '', title: 'contains'}, + {id: '!', title: 'does not contain'} + ], + text: [ + {id: '', title: 'contains'}, + {id: '!', title: 'does not contain'} + ] + }, + operators: [ + {id: '&', title: 'all'}, + {id: '|', title: 'any'} + ] + }); + + if (!self.options.query.conditions.length) { + self.options.query.conditions = [{ + key: self.options.findKeys[0].id, + value: '', + operator: self.conditionOperators[ + getConditionType(self.options.findKeys[0].type) + ][0].id + }]; + } + + self.$operator = new Ox.FormElementGroup({ + elements: [ + new Ox.Label({ + title: 'Match', + overlap: 'right', + width: 48 + }), + new Ox.FormElementGroup({ + elements: [ + new Ox.Select({ + items: self.operators, + width: 48 + }) + .bindEvent({ + change: changeOperator + }), + new Ox.Label({ + overlap: 'left', + title: 'of the following conditions', + width: 160 + }) + ], + float: 'right', + width: 208 + }) + ], + float: 'left', + }); + + self.$buttons = []; + self.$conditions = $.map(self.options.query.conditions, function(condition, i) { + return constructCondition(condition, i); + }); + + self.$limit = new Ox.InputGroup({ + inputs: [ + new Ox.Checkbox({ + width: 16 + }), + new Ox.FormElementGroup({ + elements: [ + new Ox.Input({ + width: 56 + }), + new Ox.Select({ + items: [ + {id: 'items', title: 'items'}, + {}, + {id: 'hours', title: 'hours'}, + {id: 'days', title: 'days'}, + {}, + {id: 'GB', title: 'GB'} + ], + overlap: 'left', + width: 64 + }) + ], + float: 'right', + width: 120 + }), + new Ox.Select({ + items: self.options.sortKeys, + width: 128 + }), + new Ox.FormElementGroup({ + elements: [ + new Ox.Select({ + items: [ + {id: 'ascending', title: 'ascending'}, + {id: 'descending', title: 'descending'} + ], + width: 96 + }), + new Ox.Label({ + overlap: 'left', + title: 'order', + width: 72 + }) + ], + float: 'right', + width: 168 + }) + ], + separators: [ + {title: 'Limit to', width: 56}, + {title: 'sorted by', width: 64}, + {title: 'in', width: 32} + ] + }); + + self.$view = new Ox.InputGroup({ + inputs: [ + new Ox.Checkbox({ + width: 16 + }), + new Ox.Select({ + items: self.options.viewKeys, + width: 128 + }) + ], + separators: [ + {title: 'By default, view', width: 112} + ] + }); + + self.$save = new Ox.InputGroup({ + inputs: [ + new Ox.Checkbox({ + width: 16 + }), + new Ox.Input({ + id: 'list', + width: 128 + }) + ], + separators: [ + {title: 'Save as Smart List', width: 112} + ] + }); + + self.$items = $.merge($.merge([self.$operator], self.$conditions), [self.$limit, self.$view, self.$save]); + + self.$form = new Ox.Form({ + items: self.$items + }); + that.$element = self.$form.$element; + + function addCondition(pos) { + var key = self.options.findKeys[0]; + self.options.query.conditions.splice(pos, 0, { + key: key.id, + value: '', + operator: self.conditionOperators[key.type][0].id + }); + self.$conditions.splice(pos, 0, constructCondition({}, pos)); + updateConditions(); + self.$form.addItem(pos + 1, self.$conditions[pos]); + } + + function addGroup(pos) { + self.$form.addItem(pos + 1, constructGroup(pos)) + } + + function changeConditionKey(pos, key) { + Ox.print('changeConditionKey', pos, key); + var oldOperator = self.options.query.conditions[pos].operator, + oldType = Ox.getObjectById( + self.options.findKeys, self.options.query.conditions[pos].key + ).type, + newType = Ox.getObjectById( + self.options.findKeys, key + ).type, + oldConditionType = getConditionType(oldType), + newConditionType = getConditionType(newType); + changeConditionType = oldConditionType != newConditionType; + Ox.print('old new', oldConditionType, newConditionType) + self.options.query.conditions[pos].key = key; + if (changeConditionType) { + self.$conditions[pos].replaceElement(1, constructConditionOperator(pos, oldOperator)); + } + } + + function changeConditionOperator(pos, operator) { + self.options.query.conditions[pos].operator = operator; + } + + function changeOperator(event, data) { + self.options.query.operator = data.selected[0].id; + } + + function constructCondition(condition, pos) { + var $condition; + return $condition = new Ox.FormElementGroup({ + elements: [ + new Ox.Select({ + items: $.map(self.options.findKeys, function(key) { + return { + id: key.id, + title: key.title + }; + }), + //items: $.extend({}, self.options.findKeys), // fixme: Ox.Menu messes with keys + overlap: 'right', + width: 128 + }) + .bindEvent({ + change: function(event, data) { + Ox.print('event', event) + changeConditionKey($condition.data('position'), data.selected[0].id); + } + }), + constructConditionOperator(pos), + new Ox.Input({ + width: 256 + }), + new Ox.Button({ + disabled: self.options.query.conditions.length == 1, + id: 'remove', + title: 'remove', + type: 'image' + }) + .css({margin: '0 4px 0 8px'}) + .bindEvent({ + click: function() { + removeCondition($condition.data('position')); + } + }), + new Ox.Button({ + id: 'add', + title: 'add', + type: 'image' + }) + .css({margin: '0 4px 0 4px'}) + .bindEvent({ + click: function() { + Ox.print('add', $(this).parent().parent().data('position')) + addCondition($condition.data('position') + 1) + } + }), + new Ox.Button({ + id: 'addgroup', + title: 'more', + type: 'image' + }) + .css({margin: '0 0 0 4px'}) + .bindEvent({ + click: function() { + addGroup($condition.data('position') + 1) + } + }) + ] + }) + .data({position: pos}); + } + + function constructConditionOperator(pos, selected) { + return new Ox.Select({ + items: $.map(self.conditionOperators[getConditionType( + Ox.getObjectById( + self.options.findKeys, + self.options.query.conditions[pos].key + ).type + )], function(operator) { + return { + checked: operator.id == selected, // fixme: should be "selected", not "checked" + id: operator.operator, + title: operator.title + }; + }), + overlap: 'right', + width: 128 + }) + .bindEvent({ + change: function(event, data) { + changeConditionOperator(/*$condition.data('position')*/ pos, data.selected[0].id) + } + }); + } + + function constructGroup() { + // fixme: duplicated + return new Ox.FormElementGroup({ + elements: [ + new Ox.Label({ + title: self.options.operator == '&' ? 'and' : 'or', + overlap: 'right', + width: 48 + }), + new Ox.FormElementGroup({ + elements: [ + new Ox.Select({ + items: $.map(self.operators, function(operator) { + Ox.print('!!!!', { + checked: operator.id != self.options.operator, + id: operator.id, + title: operator.title + }); + return { + //checked: operator.id != self.options.operator, + id: operator.id, + title: operator.title + } + }), + width: 48 + }) + .bindEvent({ + change: changeOperator + }), + new Ox.Label({ + overlap: 'left', + title: 'of the following conditions', + width: 160 + }) + ], + float: 'right', + width: 208 + }) + ], + float: 'left', + }); + } + + function getConditionType(type) { + type = Ox.isArray(type) ? type[0] : type; + if (['float', 'integer', 'year'].indexOf(type) > -1) { + type = 'number'; + } + return type; + } + + function removeCondition(pos) { + self.options.query.conditions.splice(pos, 1); + self.$conditions.splice(pos, 1); + updateConditions(); + self.$form.removeItem(pos + 1); + } + + function updateConditions() { + self.$conditions.forEach(function(condition, pos) { + condition.data({position: pos}); + }); + self.$conditions[0].options('elements')[3].options({ + disabled: self.options.query.conditions.length == 1 + }); + } + + return that; + +}; diff --git a/source/js/Ox.Flipbook.js b/source/js/Ox.Flipbook.js new file mode 100644 index 00000000..dd90d7e2 --- /dev/null +++ b/source/js/Ox.Flipbook.js @@ -0,0 +1,98 @@ +// fixme: rename! + +Ox.Flipbook = function(options, self) { + + var self = self || {}, + frame = $('').css({ + 'position': 'absolute', + 'width': '100%', + 'height': 'auto' + }) + .hide(), + icon = $('').css({ + 'position': 'absolute', + 'width': '100%', + 'height': 'auto' + }), + frames = {}, + timestamp = $('
').css({ + 'position': 'absolute', + 'text-align': 'center', + 'width': '100%', + }) + .hide(), + that = new Ox.Element('div', self) + .defaults({ + frames: {}, + duration: 0, + icon: '', + }) + .options(options || {}) + .append(icon) + .append(frame) + .append(timestamp) + .mouseover(function() { + frame.show(); + timestamp.show(); + icon.hide(); + }) + .mousemove(function(event) { + var position = getPosition(event), + image = getFrame(position), + frameHeight = image?image.height:that.height(); + frame.attr('src', image.src); + timestamp.html(Ox.formatDuration(position, 'short')); + + var height = (that.height() - frameHeight)/2; + frame.css({'top': height + 'px'}); + timestamp.css({'top': (frameHeight + height) + 'px'}); + }) + .mouseout(function() { + frame.hide(); + timestamp.hide(); + icon.show(); + }) + .mousedown(function(event) { + that.triggerEvent('click', { + 'position': getPosition(event) + }); + }); + + function getPosition(event) { + var position = Math.floor(event.clientX - that.offset().left); + position = (position / that.width()) * self.options.duration; + return position; + } + + function getFrame(position) { + var frame; + frames.forEach(function(img, i) { + if (!frame || i <= position) + frame = img; + }); + return frame; + } + + function cacheFrames() { + Ox.forEach(self.options.frames, function(src, i) { + frames[i] = new Image(); + frames[i].onload = function() { + frameHeight = frames[i].height / frames[i].width * that.width(); + } + frames[i].src = src; + }); + } + + self.onChange = function(key, value) { + if (key == 'frames') { + cacheFrames(); + } else if (key == 'icon') { + icon.attr('src', value); + } + } + + if(options.icon) + icon.attr('src', options.icon); + cacheFrames(); + return that; +}; diff --git a/source/js/Ox.Focus.js b/source/js/Ox.Focus.js new file mode 100644 index 00000000..eb331588 --- /dev/null +++ b/source/js/Ox.Focus.js @@ -0,0 +1,40 @@ +Ox.Focus = function() { + /*** + Ox.Focus + Basic focus handler + Methods + blur(id) blur element + focus(id) focus element + focused() return id of focused element, or null + ***/ + var stack = []; + return { + _print: function() { + Ox.print(stack); + }, + blur: function(id) { + var index = stack.indexOf(id); + if (index > -1 && index == stack.length - 1) { + stack.length == 1 ? stack.pop() : + stack.splice(stack.length - 2, 0, stack.pop()); + //$elements[id].removeClass('OxFocus'); + $('.OxFocus').removeClass('OxFocus'); // fixme: the above is better, and should work + stack.length && Ox.UI.elements[stack[stack.length - 1]].addClass('OxFocus'); + Ox.print('blur', id, stack); + } + }, + focus: function(id) { + var index = stack.indexOf(id); + if (index == -1 || index < stack.length - 1) { + index > -1 && stack.splice(index, 1); + stack.push(id); + $('.OxFocus').removeClass('OxFocus'); // fixme: see above + Ox.UI.elements[id].addClass('OxFocus'); + Ox.print('focus', id, stack); + } + }, + focused: function() { + return stack.length ? stack[stack.length - 1] : null; + } + }; +}(); diff --git a/source/js/Ox.Form.js b/source/js/Ox.Form.js new file mode 100644 index 00000000..acb52c01 --- /dev/null +++ b/source/js/Ox.Form.js @@ -0,0 +1,121 @@ +Ox.Form = function(options, self) { + + /** + */ + + var self = self || {}, + that = new Ox.Element('div', self) + .defaults({ + error: '', + id: '', + items: [], + submit: null + }) + .options(options || {}) // fixme: the || {} can be done once, in the options function + .addClass('OxForm'); + + $.extend(self, { + $items: [], + $messages: [], + formIsValid: false, + itemIds: [], + itemIsValid: [] + }); + + // fixme: form isn't necessarily empty/invalid + self.options.items.forEach(function(item, i) { + self.itemIds[i] = item.options('id') || item.id; + self.itemIsValid[i] = !!item.value().length; + that.append(self.$items[i] = new Ox.FormItem({element: item})); + item.bindEvent({ + /* + blur: function(event, data) { + validate(i, data.valid); + if (data.valid) { + self.$messages[i].html('').hide(); + } else { + self.$messages[i].html(data.message).show(); + } + }, + */ + autovalidate: function(event, data) { + data.valid = !!data.value.length; + validate(i, data.valid); + data.valid && self.$items[i].setMessage(''); + }, + submit: function(event, data) { + self.formIsValid && that.submit(); + }, + validate: function(event, data) { + validate(i, data.valid); + self.$items[i].setMessage(data.valid ? '' : data.message); + } + }); + }); + + function getItemPositionById(id) { + return self.itemIds.indexOf(id); + } + + function submitCallback(data) { + data.forEach(function(v, i) { + self.$items[i].setMessage(v.message); + }); + } + + function validate(pos, valid) { + //Ox.print('FORM validate', pos, valid) + self.itemIsValid[pos] = valid; + if (Ox.every(self.itemIsValid) != self.formIsValid) { + self.formIsValid = !self.formIsValid; + that.triggerEvent('validate', { + valid: self.formIsValid + }); + } + } + + that.addItem = function(pos, item) { + Ox.print('addItem', pos) + self.options.items.splice(pos, 0, item); + self.$items.splice(pos, 0, new Ox.FormItem({element: item})); + pos == 0 ? + self.$items[pos].insertBefore(self.$items[0]) : + self.$items[pos].insertAfter(self.$items[pos - 1]); + } + + that.removeItem = function(pos) { + Ox.print('removeItem', pos); + self.$items[pos].removeElement(); + self.options.items.splice(pos, 1); + self.$items.splice(pos, 1); + } + + that.submit = function() { + //Ox.print('---- that.values()', that.values()) + self.options.submit(that.values(), submitCallback); + }; + + that.values = function() { // fixme: can this be private? + /* + get/set form values + call without arguments to get current form values + pass values as array to set values (not implemented) + */ + var values = {}; + if (arguments.length == 0) { + self.$items.forEach(function($item, i) { + values[self.itemIds[i]] = self.$items[i].value(); + }); + //Ox.print('VALUES', values) + return values; + } else { + Ox.each(arguments[0], function(val, key) { + + }); + return that; + } + }; + + return that; + +}; diff --git a/source/js/Ox.FormElementGroup.js b/source/js/Ox.FormElementGroup.js new file mode 100644 index 00000000..103670d4 --- /dev/null +++ b/source/js/Ox.FormElementGroup.js @@ -0,0 +1,73 @@ +Ox.FormElementGroup = function(options, self) { + + var self = self || {}, + that = new Ox.Element('div', self) + .defaults({ + id: '', + elements: [], + float: 'left', + separators: [], + width: 0 + }) + .options(options || {}) + .addClass('OxInputGroup'); + + ( + self.options.float == 'left' ? + self.options.elements : self.options.elements.reverse() + ).forEach(function($element, i) { + $element.css({ + float: self.options.float // fixme: make this a class + }) + .bindEvent({ + validate: function(event, data) { + that.triggerEvent({ + validate: data + }); + } + }) + .appendTo(that); + }); + + /* + if (self.options.width) { + setWidths(); + } else { + self.options.width = getWidth(); + } + that.css({ + width: self.options.width + 'px' + }); + */ + + function getWidth() { + + } + + function setWidth() { + + } + + self.onChange = function(key, value) { + + }; + + that.replaceElement = function(pos, element) { + Ox.print('Ox.FormElementGroup replaceElement', pos, element) + self.options.elements[pos].replaceWith(element.$element); + self.options.elements[pos] = element; + }; + + that.value = function() { + return $.map(self.options.elements, function(element) { + var ret = null; + ['checked', 'selected', 'value'].forEach(function(v) { + element[v] && (ret = element[v]()); + }); + return ret; + }); + }; + + return that; + +}; diff --git a/source/js/Ox.FormItem.js b/source/js/Ox.FormItem.js new file mode 100644 index 00000000..94fadbba --- /dev/null +++ b/source/js/Ox.FormItem.js @@ -0,0 +1,27 @@ +Ox.FormItem = function(options, self) { + + var self = self || {}, + that = new Ox.Element('div', self) + .defaults({ + element: null, + error: '', + }) + .options(options || {}) + .addClass('OxFormItem') + .append(self.options.element); + + self.$message = new Ox.Element() + .addClass('OxFormMessage') + .appendTo(that); + + that.setMessage = function(message) { + self.$message.html(message)[message !== '' ? 'show' : 'hide'](); + } + + that.value = function() { + return self.options.element.value(); + }; + + return that; + +} diff --git a/source/js/Ox.History.js b/source/js/Ox.History.js new file mode 100644 index 00000000..2432f7a2 --- /dev/null +++ b/source/js/Ox.History.js @@ -0,0 +1,4 @@ +/*** + Ox.History +***/ + diff --git a/source/js/Ox.IconItem.js b/source/js/Ox.IconItem.js new file mode 100644 index 00000000..2958e81b --- /dev/null +++ b/source/js/Ox.IconItem.js @@ -0,0 +1,155 @@ +Ox.IconItem = function(options, self) { + + //Ox.print('IconItem', options, self) + + var self = self || {}, + that = new Ox.Element({}, self) + .defaults({ + height: 128, + id: '', + info: '', + size: 128, + title: '', + width: 128, + url: '' + }) + .options(options || {}) + + $.extend(self, { + fontSize: self.options.size == 64 ? 6 : 9, + height: self.options.size * 1.5, + lineLength: self.options.size == 64 ? 15 : 23, + lines: self.options.size == 64 ? 4 : 5, + url: Ox.UI.PATH + '/png/ox.ui/transparent.png', + width: self.options.size + }); + self.title = formatText(self.options.title, self.lines - 1, self.lineLength); + self.info = formatText(self.options.info, 5 - self.title.split('
').length, self.lineLength); + + that.css({ + width: self.width + 'px', + height: self.height + 'px' + }); + that.$icon = $('
') + .addClass('OxIcon') + .css({ + top: self.options.size == 64 ? -64 : -124, + width: (self.options.size + 4) + 'px', + height: (self.options.size + 4) + 'px' + }); + that.$iconImage = $('') + .addClass('OxLoading OxTarget') + .attr({ + src: self.url + }) + .css({ + width: self.options.width + 'px', + height: self.options.height + 'px' + }) + .mousedown(mousedown) + .mouseenter(mouseenter) + .mouseleave(mouseleave); + self.options.url && that.$iconImage.one('load', load); + that.$textBox = $('
') + .addClass('OxText') + .css({ + top: (self.options.size / 2) + 'px', + width: (self.options.size + 4) + 'px', + height: (self.options.size == 64 ? 30 : 58) + 'px' + }) + that.$text = $('
') + .addClass('OxTarget') + .css({ + fontSize: self.fontSize + 'px' + }) + .html( + self.title + '
' + self.info + '' + ) + .mouseenter(mouseenter) + .mouseleave(mouseleave); + that.$reflection = $('
') + .addClass('OxReflection') + .css({ + top: self.options.size + 'px', + width: (self.options.size + 4) + 'px', + height: (self.options.size / 2) + 'px' + }); + that.$reflectionImage = $('') + .addClass('OxLoading') + .attr({ + src: self.url + }) + .css({ + width: self.options.width + 'px', + height: self.options.height + 'px', + // firefox is 1px off when centering images with odd width and scaleY(-1) + paddingLeft: ($.browser.mozilla && self.options.width % 2 ? 1 : 0) + 'px' + }); + that.$gradient = $('
') + .css({ + //top: (-self.options.size / 2) + 'px', + width: self.options.width + 'px', + height: (self.options.size / 2) + 'px' + }); + + that.append( + that.$reflection.append( + that.$reflectionImage + ).append( + that.$gradient + ) + ).append( + that.$textBox.append( + that.$text + ) + ).append( + that.$icon.append( + that.$iconImage + ) + ); + + function formatText(text, maxLines, maxLength) { + var lines = Ox.wordwrap(text, maxLength, '
', true, false).split('
'); + return $.map(lines, function(line, i) { + if (i < maxLines - 1) { + return line; + } else if (i == maxLines - 1) { + return lines.length == maxLines ? line : Ox.truncate($.map(lines, function(line, i) { + return i < maxLines - 1 ? null : line; + }).join(' '), maxLength, '...', 'center'); + } else { + return null; + } + }).join('
'); + } + + function load() { + that.$iconImage.attr({ + src: self.options.url + }) + .one('load', function() { + that.$iconImage.removeClass('OxLoading'); + that.$reflectionImage + .attr({ + src: self.options.url + }) + .removeClass('OxLoading'); + }); + } + + function mousedown(e) { + // fixme: preventDefault keeps image from being draggable in safari - but also keeps the list from getting focus + // e.preventDefault(); + } + + function mouseenter() { + that.addClass('OxHover'); + } + + function mouseleave() { + that.removeClass('OxHover'); + } + + return that; + +}; diff --git a/source/js/Ox.IconList.js b/source/js/Ox.IconList.js new file mode 100644 index 00000000..c3001355 --- /dev/null +++ b/source/js/Ox.IconList.js @@ -0,0 +1,138 @@ +Ox.IconList = function(options, self) { + + var self = self || {}, + that = new Ox.Element({}, self) + .defaults({ + centerSelection: false, + draggable: true, + id: '', + item: null, + items: null, + keys: [], + max: -1, + min: 0, + orientation: 'both', + selected: [], + size: 128, + sort: [], + }) + .options(options || {}); + + $.extend(self, { + itemHeight: self.options.size * 1.5, + itemWidth: self.options.size + }); + + that.$element = new Ox.List({ + centered: self.options.centered, + construct: constructItem, + draggable: self.options.draggable, + id: self.options.id, + itemHeight: self.itemHeight, + items: self.options.items, + itemWidth: self.itemWidth, + keys: self.options.keys, + orientation: self.options.orientation, + keys: self.options.keys, + max: self.options.max, + min: self.options.min, + selected: self.options.selected, + size: self.options.size, + sort: self.options.sort, + type: 'icon', + unique: self.options.unique + }, $.extend({}, self)) // pass event handler + .addClass('OxIconList Ox' + Ox.toTitleCase(self.options.orientation)) + .click(click) + .dblclick(dblclick) + .scroll(scroll); + + updateKeys(); + + function click() { + + } + + function constructItem(data) { + var data = !$.isEmptyObject(data) ? + self.options.item(data, self.options.sort, self.options.size) : + {height: 8, width: 5}, + ratio = data.width / data.height; + return new Ox.IconItem($.extend(data, { + height: Math.round(self.options.size / (ratio <= 1 ? 1 : ratio)), + size: self.options.size, + width: Math.round(self.options.size * (ratio >= 1 ? 1 : ratio)) + })); + } + + function dblclick() { + + } + + function scroll() { + + } + + function updateKeys() { + self.options.keys = Ox.unique($.merge(self.options.keys, [self.options.sort[0].key])); + that.$element.options({ + keys: self.options.keys + }); + } + + self.onChange = function(key, value) { + if (key == 'items') { + that.$element.options(key, value); + } else if (key == 'paste') { + that.$element.options(key, value); + } else if (key == 'selected') { + that.$element.options(key, value); + } + } + + that.closePreview = function() { + that.$element.closePreview(); + }; + + that.paste = function(data) { + that.$element.paste(data); + return that; + }; + + that.reloadList = function() { + that.$element.reloadList(); + return that; + }; + + that.scrollToSelection = function() { + that.$element.scrollToSelection(); + }; + + that.size = function() { + that.$element.size(); + }; + + that.sortList = function(key, operator) { + self.options.sort = [{ + key: key, + operator: operator + }]; + updateKeys(); + that.$element.sortList(key, operator); + }; + + that.value = function(id, key, value) { + // fixme: make this accept id, {k: v, ...} + if (arguments.length == 1) { + return that.$element.value(id); + } else if (arguments.length == 2) { + return that.$element.value(id, key); + } else { + that.$element.value(id, key, value); + return that; + } + } + + return that; + +}; diff --git a/source/js/Ox.Input.js b/source/js/Ox.Input.js new file mode 100644 index 00000000..1d4009d3 --- /dev/null +++ b/source/js/Ox.Input.js @@ -0,0 +1,1762 @@ +Ox.Input = function(options, self) { + + /** + options + arrows boolearn, if true, and type is 'float' or 'integer', display arrows + arrowStep number, step when clicking arrows + autocomplete array of possible values, or + function(key, value, callback), returns one or more values + autocompleteReplace boolean, if true, value is replaced + autocompleteReplaceCorrect boolean, if true, only valid values can be entered + autocompleteSelect boolean, if true, menu is displayed + autocompleteSelectHighlight boolean, if true, value in menu is highlighted + autocompleteSelectSubmit boolean, if true, submit input on menu selection + autocorrect string ('email', 'float', 'integer', 'phone', 'url'), or + regexp(value), or + function(key, value, blur, callback), returns value + autovalidate --remote validation-- + clear boolean, if true, has clear button + disabled boolean, if true, is disabled + height integer, px (for type='textarea' and type='range' with orientation='horizontal') + id string, element id + key string, to be passed to autocomplete and autovalidate functions + max number, max value if type is 'integer' or 'float' + min number, min value if type is 'integer' or 'float' + name string, will be displayed by autovalidate function ('invalid ' + name) + overlap string, '', 'left' or 'right', will cause padding and negative margin + picker + //rangeOptions + arrows boolean, if true, display arrows + //arrowStep number, step when clicking arrows + //arrowSymbols array of two strings + max number, maximum value + min number, minimum value + orientation 'horizontal' or 'vertical' + step number, step + thumbValue boolean, if true, value is displayed on thumb, or + array of strings per value, or + function(value), returns string + thumbSize integer, px + trackGradient string, css gradient for track + trackImage string, image url, or + array of image urls + //trackStep number, 0 for 'scroll here', positive for step + trackValues boolean + serialize + textAlign 'left', 'center' or 'right' + type 'float', 'integer', 'password', 'text', 'textarea' + value string + validate function, remote validation + width integer, px + methods: + events: + change + submit + */ + + var self = self || {}, + that = new Ox.Element('div', self) + .defaults({ + arrows: false, + arrowStep: 1, + autocomplete: null, + autocompleteReplace: false, + autocompleteReplaceCorrect: false, + autocompleteSelect: false, + autocompleteSelectHighlight: false, + autocompleteSelectSubmit: false, + autovalidate: null, + clear: false, + disabled: false, + key: '', + min: 0, + max: 100, + label: '', + labelWidth: 64, + overlap: 'none', + placeholder: '', + serialize: null, + style: 'rounded', + textAlign: 'left', + type: 'text', + validate: null, + value: '', + width: 128 + }) + .options(options) + .addClass( + 'OxInput OxMedium Ox' + Ox.toTitleCase(self.options.style) /*+ ( + self.options.overlap != 'none' ? + ' OxOverlap' + Ox.toTitleCase(self.options.overlap) : '' + )*/ + ) + .bindEvent($.extend(self.options.type == 'textarea' ? {} : { + key_enter: submit + }, { + key_control_v: paste, + key_escape: cancel + })); + + if ( + Ox.isArray(self.options.autocomplete) && + self.options.autocompleteReplace && + self.options.autocompleteReplaceCorrect && + self.options.value === '' + ) { + self.options.value = self.options.autocomplete[0] + } + + // fixme: set to min, not 0 + if (self.options.type == 'float') { + $.extend(self.options, { + autovalidate: 'float', + textAlign: 'right', + value: self.options.value || '0.0' + }); + } else if (self.options.type == 'integer') { + $.extend(self.options, { + autovalidate: 'integer', + textAlign: 'right', + value: self.options.value || '0' + }); + } + + if (self.options.label) { + self.$label = new Ox.Label({ + overlap: 'right', + textAlign: 'right', + title: self.options.label, + width: self.options.labelWidth + }) + .css({ + float: 'left', // fixme: use css rule + }) + .click(function() { + // fixme: ??? + // that.focus(); + }) + .appendTo(that); + } + + if (self.options.arrows) { + self.arrows = []; + self.arrows[0] = [ + new Ox.Button({ + overlap: 'right', + title: 'left', + type: 'image' + }) + .css({ + float: 'left' + }) + .click(function() { + clickArrow(0); + }) + .appendTo(that), + new Ox.Button({ + overlap: 'left', + title: 'right', + type: 'image' + }) + .css({ + float: 'right' + }) + .click(function() { + clickArrow(1); + }) + .appendTo(that) + ] + } + + $.extend(self, { + bindKeyboard: self.options.autocomplete || self.options.autovalidate, + hasPasswordPlaceholder: self.options.type == 'password' && self.options.placeholder, + inputWidth: getInputWidth() + }); + + if (self.options.clear) { + self.$button = new Ox.Button({ + overlap: 'left', + title: 'close', + type: 'image' + }) + .css({ + float: 'right' // fixme: use css rule + }) + .click(clear) + .appendTo(that); + } + + self.$input = $(self.options.type == 'textarea' ? '