/********************************************************************** * * * **********************************************************************/ define(function(require){ var module = {} //var DEBUG = DEBUG != null ? DEBUG : true /*********************************************************************/ String.prototype.capitalize = function(){ return this[0].toUpperCase() + this.slice(1) } // XXX not sure if this has to be a utility or a method... Object.get = function(obj, name, dfl){ var val = obj[name] if(val === undefined && dfl != null){ return dfl } return val } // Compact a sparse array... // // NOTE: this will not compact in-place. Array.prototype.compact = function(){ return this.filter(function(){ return true }) } /* Array.prototype.compact = function(){ var res = [] for(var i in res){ res.push(this[i]) } return res } */ // return an array with duplicate elements removed... // Array.prototype.unique = function(normalize){ if(normalize){ var cache = this.map(function(e){ return normalize(e) }) return this.filter(function(e, i, a){ return cache.indexOf(cache[i]) == i }) } else { return this.filter(function(e, i, a){ return a.indexOf(e) == i }) } } // Compare two arrays... // Array.prototype.cmp = function(other){ if(this === other){ return true } if(this.length != other.length){ return false } for(var i=0; i])/g, '\\$1') } // XXX do we need to quote anything else??? var path2url = module.path2url = function(path){ // test if we have a schema, and if yes return as-is... if(/^(http|https|file|[\w-]*):[\\\/]{2}/.test(path)){ return path } // skip encoding windows drives... var drive = path.split(/^([a-z]:[\\\/])/i) path = drive.pop() drive = drive.pop() || '' return drive + (path .split(/[\\\/]/g) // XXX these are too aggressive... //.map(encodeURI) //.map(encodeURIComponent) .join('/') // NOTE: keep '%' the first... //.replace(/%/g, '%25') .replace(/#/g, '%23') .replace(/&/g, '%26')) } // NOTE: we are not using node's path module as we need this to work in // all contexts, not only node... (???) var normalizePath = module.normalizePath = function(path){ return typeof(path) == typeof('str') ? path // normalize the slashes... .replace(/\\/g, '/') // remove duplicate '/' .replace(/(\/)\1+/g, '/') // remove trailing '/' .replace(/\/+$/, '') // take care of . .replace(/\/\.\//g, '/') .replace(/\/\.$/, '') // take care of .. .replace(/\/[^\/]+\/\.\.\//g, '/') .replace(/\/[^\/]+\/\.\.$/, '') : path } /*********************************************************************/ module.selectElemText = function(elem){ var range = document.createRange() range.selectNodeContents(elem) var sel = window.getSelection() sel.removeAllRanges() sel.addRange(range) } /*********************************************************************/ // NOTE: repatching a date should not lead to any side effects as this // does not add any state... var patchDate = module.patchDate = function(date){ date = date || Date date.prototype.toShortDate = function(){ var y = this.getFullYear() var M = this.getMonth()+1 M = M < 10 ? '0'+M : M var D = this.getDate() D = D < 10 ? '0'+D : D var H = this.getHours() H = H < 10 ? '0'+H : H var m = this.getMinutes() m = m < 10 ? '0'+m : m var s = this.getSeconds() s = s < 10 ? '0'+s : s return ''+y+'-'+M+'-'+D+' '+H+':'+m+':'+s } date.prototype.getTimeStamp = function(no_seconds){ var y = this.getFullYear() var M = this.getMonth()+1 M = M < 10 ? '0'+M : M var D = this.getDate() D = D < 10 ? '0'+D : D var H = this.getHours() H = H < 10 ? '0'+H : H var m = this.getMinutes() m = m < 10 ? '0'+m : m var s = this.getSeconds() s = s < 10 ? '0'+s : s return ''+y+M+D+H+m+s } date.prototype.setTimeStamp = function(ts){ ts = ts.replace(/[^0-9]*/g, '') this.setFullYear(ts.slice(0, 4)) this.setMonth(ts.slice(4, 6)*1-1) this.setDate(ts.slice(6, 8)) this.setHours(ts.slice(8, 10)) this.setMinutes(ts.slice(10, 12)) this.setSeconds(ts.slice(12, 14)) return this } date.timeStamp = function(){ return (new this()).getTimeStamp() } date.fromTimeStamp = function(ts){ return (new this()).setTimeStamp(ts) } // convert string time period to milliseconds... date.str2ms = function(str, dfl){ dfl = dfl || 'ms' if(typeof(str) == typeof(123)){ var val = str str = dfl } else { var val = parseFloat(str) str = str.trim() // check if a unit is given... str = str == val ? dfl : str } var c = /(m(illi)?(-)?s(ec(ond(s)?)?)?)$/i.test(str) ? 1 : /s(ec(ond(s)?)?)?$/i.test(str) ? 1000 : /m(in(ute(s)?)?)?$/i.test(str) ? 1000*60 : /h(our(s)?)?$/i.test(str) ? 1000*60*60 : /d(ay(s)?)?$/i.test(str) ? 1000*60*60*24 : null return c ? val * c : NaN } return date } // patch the root date... patchDate() /*********************************************************************/ // XXX experiment if(typeof(jQuery) != typeof(undefined)){ jQuery.fn._drag = function(){ var dragging = false var s, px, py var elem = $(this) .on('mousedown touchstart', function(evt){ dragging = true px = evt.clientX px = evt.clientY s = elem.rscale() }) .on('mousemove touchmove', function(evt){ if(!dragging){ return } var x = evt.clientX var dx = px - x px = x var y = evt.clientY var dy = py - y py = y elem .velocity('stop') .velocity({ translateX: '-=' + (dx / s), translateY: '-=' + (dy / s), }, 0) }) .on('mouseup touchend', function(evt){ dragging = false elem.velocity('stop') }) } jQuery.fn.selectText = function(){ var range = document.createRange() this.each(function(){ range.selectNodeContents(this) }) var sel = window.getSelection() sel.removeAllRanges() sel.addRange(range) return this } var keyboard = require('lib/keyboard') // Make element editable... // // Options format: // { // multiline: false, // // reset_on_abort: true, // clear_on_edit: true, // // abort_keys: [ // 'Esc', // ... // ], // } // // This listens to these events triggerable by user: // 'commit' - will commit changes and fire 'edit-done' with // field text. // 'abort' - will reset field and trigger 'edit-aborted' // with original (before edit started) field text // // XXX should we just use form elements??? // ...it's a trade-off, here we add editing functionality and fight // a bit the original function, in an input we'll need to fight part // of the editing functionality and add our own navigation... // XXX move this to a more generic spot... // XXX should this reset field to it's original state after // commit/abort??? jQuery.fn.makeEditable = function(options){ options = options || {} var that = this var original = this.text() if(options.clear_on_edit == null || options.clear_on_edit){ this.text('') } this .prop('contenteditable', true) // make the element focusable and selectable... .attr('tabindex', '0') .addClass('editable-field') // NOTE: this will also focus the element... .selectText() .keydown(function(){ if(!that.prop('contenteditable')){ return } event.stopPropagation() var n = keyboard.toKeyName(event.keyCode) // abort... if((options.abort_keys || ['Esc']).indexOf(n) >= 0){ // reset original value... (options.reset_on_abort == null || options.reset_on_abort) && that.text(original) that.trigger('abort') // done -- single line... } else if(n == 'Enter' && !options.multiline){ event.preventDefault() that.trigger('commit') // done -- multiline... } else if(n == 'Enter' && (event.ctrlKey || event.metaKey) && options.multiline){ event.preventDefault() that.trigger('commit') } }) // user triggerable events... .on('abort', function(){ that.trigger('edit-aborted', original) }) .on('commit', function(){ that.trigger('edit-done', that.text()) }) return this } } /********************************************************************** * vim:set ts=4 sw=4 : */ return module })