diff options
Diffstat (limited to 'simple/simple-http/src/test/java/org/simpleframework/http/socket/table/w2ui-1.4.js')
-rw-r--r-- | simple/simple-http/src/test/java/org/simpleframework/http/socket/table/w2ui-1.4.js | 13617 |
1 files changed, 13617 insertions, 0 deletions
diff --git a/simple/simple-http/src/test/java/org/simpleframework/http/socket/table/w2ui-1.4.js b/simple/simple-http/src/test/java/org/simpleframework/http/socket/table/w2ui-1.4.js new file mode 100644 index 0000000..afdc6a0 --- /dev/null +++ b/simple/simple-http/src/test/java/org/simpleframework/http/socket/table/w2ui-1.4.js @@ -0,0 +1,13617 @@ +/* w2ui 1.4 (c) http://w2ui.com, vitmalina@gmail.com */ +var w2ui = w2ui || {}; +var w2obj = w2obj || {}; // expose object to be able to overwrite default functions + +/************************************************ +* Library: Web 2.0 UI for jQuery +* - Following objects are defines +* - w2ui - object that will contain all widgets +* - w2obj - object with widget prototypes +* - w2utils - basic utilities +* - $().w2render - common render +* - $().w2destroy - common destroy +* - $().w2marker - marker plugin +* - $().w2tag - tag plugin +* - $().w2overlay - overlay plugin +* - $().w2menu - menu plugin +* - w2utils.event - generic event object +* - w2utils.keyboard - object for keyboard navigation +* - Dependencies: jQuery +* +* == NICE TO HAVE == +* - date has problems in FF new Date('yyyy-mm-dd') breaks +* - bug: w2utils.formatDate('2011-31-01', 'yyyy-dd-mm'); - wrong foratter +* - overlay should be displayed where more space (on top or on bottom) +* - write and article how to replace certain framework functions +* - format date and time is buggy +* - onComplete should pass widget as context (this) +* - add maxHeight for the w2menu +* - user localization from another lib (make it generic), https://github.com/jquery/globalize#readme +* - hidden and disabled in menus +* - isTime should support seconds +* - TEST On IOS +* +************************************************/ + +var w2utils = (function () { + var tmp = {}; // for some temp variables + var obj = { + version : '1.4.0', + settings : { + "locale" : "en-us", + "date_format" : "m/d/yyyy", + "date_display" : "Mon d, yyyy", + "time_format" : "h12", + "currencyPrefix" : "$", + "currencySuffix" : "", + "currencyPrecision" : 2, + "groupSymbol" : ",", + "shortmonths" : ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"], + "fullmonths" : ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"], + "shortdays" : ["M", "T", "W", "T", "F", "S", "S"], + "fulldays" : ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"], + "dataType" : 'HTTP', // can be HTTP, RESTFULL, JSON (case sensative) + "phrases" : {} // empty object for english phrases + }, + isInt : isInt, + isFloat : isFloat, + isMoney : isMoney, + isHex : isHex, + isAlphaNumeric : isAlphaNumeric, + isEmail : isEmail, + isDate : isDate, + isTime : isTime, + age : age, + date : date, + size : size, + formatNumber : formatNumber, + formatDate : formatDate, + formatTime : formatTime, + formatDateTime : formatDateTime, + stripTags : stripTags, + encodeTags : encodeTags, + escapeId : escapeId, + base64encode : base64encode, + base64decode : base64decode, + transition : transition, + lock : lock, + unlock : unlock, + lang : lang, + locale : locale, + getSize : getSize, + scrollBarSize : scrollBarSize, + checkName : checkName, + checkUniqueId : checkUniqueId, + parseRoute : parseRoute, + // some internal variables + isIOS : ((navigator.userAgent.toLowerCase().indexOf('iphone') != -1 || + navigator.userAgent.toLowerCase().indexOf('ipod') != -1 || + navigator.userAgent.toLowerCase().indexOf('ipad') != -1) + ? true : false), + isIE : ((navigator.userAgent.toLowerCase().indexOf('msie') != -1 || + navigator.userAgent.toLowerCase().indexOf('trident') != -1 ) + ? true : false) + }; + return obj; + + function isInt (val) { + var re = /^[-+]?[0-9]+$/; + return re.test(val); + } + + function isFloat (val) { + return (typeof val === 'number' || (typeof val === 'string' && val !== '')) && !isNaN(Number(val)); + } + + function isMoney (val) { + var se = w2utils.settings; + var re = new RegExp('^'+ (se.currencyPrefix ? '\\' + se.currencyPrefix + '?' : '') +'[-+]?[0-9]*[\.]?[0-9]+'+ (se.currencySuffix ? '\\' + se.currencySuffix + '?' : '') +'$', 'i'); + if (typeof val === 'string') { + val = val.replace(new RegExp(se.groupSymbol, 'g'), ''); + } + if (typeof val === 'object' || val === '') return false; + return re.test(val); + } + + function isHex (val) { + var re = /^[a-fA-F0-9]+$/; + return re.test(val); + } + + function isAlphaNumeric (val) { + var re = /^[a-zA-Z0-9_-]+$/; + return re.test(val); + } + + function isEmail (val) { + var email = /^[a-zA-Z0-9._%-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,4}$/; + return email.test(val); + } + + function isDate (val, format, retDate) { + if (!val) return false; + + var dt = 'Invalid Date'; + var month, day, year; + + if (format == null) format = w2utils.settings.date_format; + + if (typeof val.getUTCFullYear === 'function' && typeof val.getUTCMonth === 'function' && typeof val.getUTCDate === 'function') { + year = val.getUTCFullYear(); + month = val.getUTCMonth(); + day = val.getUTCDate(); + } else if (typeof val.getFullYear === 'function' && typeof val.getMonth === 'function' && typeof val.getDate === 'function') { + year = val.getFullYear(); + month = val.getMonth(); + day = val.getDate(); + } else { + val = String(val); + // convert month formats + if (RegExp('mon', 'ig').test(format)) { + format = format.replace(/month/ig, 'm').replace(/mon/ig, 'm').replace(/dd/ig, 'd').replace(/[, ]/ig, '/').replace(/\/\//g, '/').toLowerCase(); + val = val.replace(/[, ]/ig, '/').replace(/\/\//g, '/').toLowerCase(); + for (var m = 0, len = w2utils.settings.fullmonths.length; m < len; m++) { + var t = w2utils.settings.fullmonths[m]; + val = val.replace(RegExp(t, 'ig'), (parseInt(m) + 1)).replace(RegExp(t.substr(0, 3), 'ig'), (parseInt(m) + 1)); + } + } + // format date + var tmp = val.replace(/-/g, '/').replace(/\./g, '/').toLowerCase().split('/'); + var tmp2 = format.replace(/-/g, '/').replace(/\./g, '/').toLowerCase(); + if (tmp2 === 'mm/dd/yyyy') { month = tmp[0]; day = tmp[1]; year = tmp[2]; } + if (tmp2 === 'm/d/yyyy') { month = tmp[0]; day = tmp[1]; year = tmp[2]; } + if (tmp2 === 'dd/mm/yyyy') { month = tmp[1]; day = tmp[0]; year = tmp[2]; } + if (tmp2 === 'd/m/yyyy') { month = tmp[1]; day = tmp[0]; year = tmp[2]; } + if (tmp2 === 'yyyy/dd/mm') { month = tmp[2]; day = tmp[1]; year = tmp[0]; } + if (tmp2 === 'yyyy/d/m') { month = tmp[2]; day = tmp[1]; year = tmp[0]; } + if (tmp2 === 'yyyy/mm/dd') { month = tmp[1]; day = tmp[2]; year = tmp[0]; } + if (tmp2 === 'yyyy/m/d') { month = tmp[1]; day = tmp[2]; year = tmp[0]; } + if (tmp2 === 'mm/dd/yy') { month = tmp[0]; day = tmp[1]; year = tmp[2]; } + if (tmp2 === 'm/d/yy') { month = tmp[0]; day = tmp[1]; year = parseInt(tmp[2]) + 1900; } + if (tmp2 === 'dd/mm/yy') { month = tmp[1]; day = tmp[0]; year = parseInt(tmp[2]) + 1900; } + if (tmp2 === 'd/m/yy') { month = tmp[1]; day = tmp[0]; year = parseInt(tmp[2]) + 1900; } + if (tmp2 === 'yy/dd/mm') { month = tmp[2]; day = tmp[1]; year = parseInt(tmp[0]) + 1900; } + if (tmp2 === 'yy/d/m') { month = tmp[2]; day = tmp[1]; year = parseInt(tmp[0]) + 1900; } + if (tmp2 === 'yy/mm/dd') { month = tmp[1]; day = tmp[2]; year = parseInt(tmp[0]) + 1900; } + if (tmp2 === 'yy/m/d') { month = tmp[1]; day = tmp[2]; year = parseInt(tmp[0]) + 1900; } + } + if (!isInt(year)) return false; + if (!isInt(month)) return false; + if (!isInt(day)) return false; + year = +year; + month = +month; + day = +day; + dt = new Date(year, month - 1, day); + // do checks + if (month == null) return false; + if (dt === 'Invalid Date') return false; + if ((dt.getMonth() + 1 !== month) || (dt.getDate() !== day) || (dt.getFullYear() !== year)) return false; + if (retDate === true) return dt; else return true; + } + + function isTime (val, retTime) { + // Both formats 10:20pm and 22:20 + if (val == null) return false; + var max, pm; + // -- process american format + val = String(val); + val = val.toUpperCase(); + pm = val.indexOf('PM') >= 0; + var ampm = (pm || val.indexOf('AM') >= 0); + if (ampm) max = 12; else max = 24; + val = val.replace('AM', '').replace('PM', ''); + val = $.trim(val); + // --- + var tmp = val.split(':'); + var h = parseInt(tmp[0] || 0), m = parseInt(tmp[1] || 0); + // accept edge case: 3PM is a good timestamp, but 3 (without AM or PM) is NOT: + if ((!ampm || tmp.length !== 1) && tmp.length !== 2) { return false; } + if (tmp[0] === '' || h < 0 || h > max || !this.isInt(tmp[0]) || tmp[0].length > 2) { return false; } + if (tmp.length === 2 && (tmp[1] === '' || m < 0 || m > 59 || !this.isInt(tmp[1]) || tmp[1].length !== 2)) { return false; } + // check the edge cases: 12:01AM is ok, as is 12:01PM, but 24:01 is NOT ok while 24:00 is (midnight; equivalent to 00:00). + // meanwhile, there is 00:00 which is ok, but 0AM nor 0PM are okay, while 0:01AM and 0:00AM are. + if (!ampm && max === h && m !== 0) { return false; } + if (ampm && tmp.length === 1 && h === 0) { return false; } + + if (retTime === true) { + if (pm) h += 12; + return { + hours: h, + minutes: m + }; + } + return true; + } + + function age (dateStr) { + if (dateStr === '' || dateStr == null) return ''; + var d1 = new Date(dateStr); + if (w2utils.isInt(dateStr)) d1 = new Date(Number(dateStr)); // for unix timestamps + if (d1 === 'Invalid Date') return ''; + + var d2 = new Date(); + var sec = (d2.getTime() - d1.getTime()) / 1000; + var amount = ''; + var type = ''; + if (sec < 0) { + amount = '<span style="color: #aaa">future</span>'; + type = ''; + } else if (sec < 60) { + amount = Math.floor(sec); + type = 'sec'; + if (sec < 0) { amount = 0; type = 'sec'; } + } else if (sec < 60*60) { + amount = Math.floor(sec/60); + type = 'min'; + } else if (sec < 24*60*60) { + amount = Math.floor(sec/60/60); + type = 'hour'; + } else if (sec < 30*24*60*60) { + amount = Math.floor(sec/24/60/60); + type = 'day'; + } else if (sec < 365.25*24*60*60) { + amount = Math.floor(sec/365.25/24/60/60*10)/10; + type = 'month'; + } else if (sec >= 365.25*24*60*60) { + amount = Math.floor(sec/365.25/24/60/60*10)/10; + type = 'year'; + } + return amount + ' ' + type + (amount > 1 ? 's' : ''); + } + + function date (dateStr) { + if (dateStr === '' || dateStr == null) return ''; + var d1 = new Date(dateStr); + if (w2utils.isInt(dateStr)) d1 = new Date(Number(dateStr)); // for unix timestamps + if (d1 === 'Invalid Date') return ''; + + var months = w2utils.settings.shortmonths; + var d2 = new Date(); // today + var d3 = new Date(); + d3.setTime(d3.getTime() - 86400000); // yesterday + + var dd1 = months[d1.getMonth()] + ' ' + d1.getDate() + ', ' + d1.getFullYear(); + var dd2 = months[d2.getMonth()] + ' ' + d2.getDate() + ', ' + d2.getFullYear(); + var dd3 = months[d3.getMonth()] + ' ' + d3.getDate() + ', ' + d3.getFullYear(); + + var time = (d1.getHours() - (d1.getHours() > 12 ? 12 :0)) + ':' + (d1.getMinutes() < 10 ? '0' : '') + d1.getMinutes() + ' ' + (d1.getHours() >= 12 ? 'pm' : 'am'); + var time2= (d1.getHours() - (d1.getHours() > 12 ? 12 :0)) + ':' + (d1.getMinutes() < 10 ? '0' : '') + d1.getMinutes() + ':' + (d1.getSeconds() < 10 ? '0' : '') + d1.getSeconds() + ' ' + (d1.getHours() >= 12 ? 'pm' : 'am'); + var dsp = dd1; + if (dd1 === dd2) dsp = time; + if (dd1 === dd3) dsp = w2utils.lang('Yesterday'); + + return '<span title="'+ dd1 +' ' + time2 +'">'+ dsp +'</span>'; + } + + function size (sizeStr) { + if (!w2utils.isFloat(sizeStr) || sizeStr === '') return ''; + sizeStr = parseFloat(sizeStr); + if (sizeStr === 0) return 0; + var sizes = ['Bt', 'KB', 'MB', 'GB', 'TB']; + var i = parseInt( Math.floor( Math.log(sizeStr) / Math.log(1024) ) ); + return (Math.floor(sizeStr / Math.pow(1024, i) * 10) / 10).toFixed(i === 0 ? 0 : 1) + ' ' + sizes[i]; + } + + function formatNumber (val, groupSymbol) { + var ret = ''; + if (groupSymbol == null) groupSymbol = w2utils.settings.groupSymbol || ','; + // check if this is a number + if (w2utils.isFloat(val) || w2utils.isInt(val) || w2utils.isMoney(val)) { + tmp = String(val).split('.'); + ret = String(tmp[0]).replace(/(\d)(?=(\d\d\d)+(?!\d))/g, "$1" + groupSymbol); + if (tmp[1] != null) ret += '.' + tmp[1]; + } + return ret; + } + + function formatDate (dateStr, format) { // IMPORTANT dateStr HAS TO BE valid JavaScript Date String + var months = w2utils.settings.shortmonths; + var fullMonths = w2utils.settings.fullmonths; + if (!format) format = this.settings.date_format; + if (dateStr === '' || dateStr == null) return ''; + + var dt = new Date(dateStr); + if (w2utils.isInt(dateStr)) dt = new Date(Number(dateStr)); // for unix timestamps + if (dt === 'Invalid Date') return ''; + + var year = dt.getFullYear(); + var month = dt.getMonth(); + var date = dt.getDate(); + return format.toLowerCase() + .replace('month', w2utils.settings.fullmonths[month]) + .replace('mon', w2utils.settings.shortmonths[month]) + .replace(/yyyy/g, year) + .replace(/yyy/g, year) + .replace(/yy/g, year > 2000 ? 100 + parseInt(String(year).substr(2)) : String(year).substr(2)) + .replace(/(^|[^a-z$])y/g, '$1' + year) // only y's that are not preceeded by a letter + .replace(/mm/g, (month + 1 < 10 ? '0' : '') + (month + 1)) + .replace(/dd/g, (date < 10 ? '0' : '') + date) + .replace(/(^|[^a-z$])m/g, '$1' + (month + 1)) // only y's that are not preceeded by a letter + .replace(/(^|[^a-z$])d/g, '$1' + date); // only y's that are not preceeded by a letter + } + + function formatTime (dateStr, format) { // IMPORTANT dateStr HAS TO BE valid JavaScript Date String + var months = w2utils.settings.shortmonths; + var fullMonths = w2utils.settings.fullmonths; + if (!format) format = (this.settings.time_format === 'h12' ? 'hh:mi pm' : 'h24:mi'); + if (dateStr === '' || dateStr == null) return ''; + + var dt = new Date(dateStr); + if (w2utils.isInt(dateStr)) dt = new Date(Number(dateStr)); // for unix timestamps + if (w2utils.isTime(dateStr)) { + var tmp = w2utils.isTime(dateStr, true); + dt = new Date(); + dt.setHours(tmp.hours); + dt.setMinutes(tmp.minutes); + } + if (dt === 'Invalid Date') return ''; + + var type = 'am'; + var hour = dt.getHours(); + var h24 = dt.getHours(); + var min = dt.getMinutes(); + var sec = dt.getSeconds(); + if (min < 10) min = '0' + min; + if (sec < 10) sec = '0' + sec; + if (format.indexOf('am') !== -1 || format.indexOf('pm') !== -1) { + if (hour >= 12) type = 'pm'; + if (hour > 12) hour = hour - 12; + } + return format.toLowerCase() + .replace('am', type) + .replace('pm', type) + .replace('hh', hour) + .replace('h24', h24) + .replace('mm', min) + .replace('mi', min) + .replace('ss', sec) + .replace(/(^|[^a-z$])h/g, '$1' + hour) // only y's that are not preceeded by a letter + .replace(/(^|[^a-z$])m/g, '$1' + min) // only y's that are not preceeded by a letter + .replace(/(^|[^a-z$])s/g, '$1' + sec); // only y's that are not preceeded by a letter + } + + function formatDateTime(dateStr, format) { + var fmt; + if (typeof format !== 'string') { + fmt = [this.settings.date_format, this.settings.time_format]; + } else { + fmt = format.split('|'); + } + return this.formatDate(dateStr, fmt[0]) + ' ' + this.formatTime(dateStr, fmt[1]); + } + + function stripTags (html) { + if (html === null) return html; + switch (typeof html) { + case 'number': + break; + case 'string': + html = $.trim(String(html).replace(/(<([^>]+)>)/ig, "")); + break; + case 'object': + for (var a in html) html[a] = this.stripTags(html[a]); + break; + } + return html; + } + + function encodeTags (html) { + if (html === null) return html; + switch (typeof html) { + case 'number': + break; + case 'string': + html = String(html).replace(/&/g, "&").replace(/>/g, ">").replace(/</g, "<").replace(/"/g, """); + break; + case 'object': + for (var a in html) html[a] = this.encodeTags(html[a]); + break; + } + return html; + } + + function escapeId (id) { + if (id === '' || id == null) return ''; + return String(id).replace(/([;&,\.\+\*\~'`:"\!\^#$%@\[\]\(\)=<>\|\/? {}\\])/g, '\\$1'); + } + + function base64encode (input) { + var output = ""; + var chr1, chr2, chr3, enc1, enc2, enc3, enc4; + var i = 0; + var keyStr = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="; + input = utf8_encode(input); + + while (i < input.length) { + chr1 = input.charCodeAt(i++); + chr2 = input.charCodeAt(i++); + chr3 = input.charCodeAt(i++); + enc1 = chr1 >> 2; + enc2 = ((chr1 & 3) << 4) | (chr2 >> 4); + enc3 = ((chr2 & 15) << 2) | (chr3 >> 6); + enc4 = chr3 & 63; + if (isNaN(chr2)) { + enc3 = enc4 = 64; + } else if (isNaN(chr3)) { + enc4 = 64; + } + output = output + keyStr.charAt(enc1) + keyStr.charAt(enc2) + keyStr.charAt(enc3) + keyStr.charAt(enc4); + } + + function utf8_encode (string) { + var string = String(string).replace(/\r\n/g,"\n"); + var utftext = ""; + + for (var n = 0; n < string.length; n++) { + var c = string.charCodeAt(n); + if (c < 128) { + utftext += String.fromCharCode(c); + } + else if((c > 127) && (c < 2048)) { + utftext += String.fromCharCode((c >> 6) | 192); + utftext += String.fromCharCode((c & 63) | 128); + } + else { + utftext += String.fromCharCode((c >> 12) | 224); + utftext += String.fromCharCode(((c >> 6) & 63) | 128); + utftext += String.fromCharCode((c & 63) | 128); + } + } + return utftext; + } + + return output; + } + + function base64decode (input) { + var output = ""; + var chr1, chr2, chr3; + var enc1, enc2, enc3, enc4; + var i = 0; + var keyStr = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="; + input = input.replace(/[^A-Za-z0-9\+\/\=]/g, ""); + + while (i < input.length) { + enc1 = keyStr.indexOf(input.charAt(i++)); + enc2 = keyStr.indexOf(input.charAt(i++)); + enc3 = keyStr.indexOf(input.charAt(i++)); + enc4 = keyStr.indexOf(input.charAt(i++)); + chr1 = (enc1 << 2) | (enc2 >> 4); + chr2 = ((enc2 & 15) << 4) | (enc3 >> 2); + chr3 = ((enc3 & 3) << 6) | enc4; + output = output + String.fromCharCode(chr1); + if (enc3 !== 64) { + output = output + String.fromCharCode(chr2); + } + if (enc4 !== 64) { + output = output + String.fromCharCode(chr3); + } + } + output = utf8_decode(output); + + function utf8_decode (utftext) { + var string = ""; + var i = 0; + var c = 0, c2, c3; + + while ( i < utftext.length ) { + c = utftext.charCodeAt(i); + if (c < 128) { + string += String.fromCharCode(c); + i++; + } + else if((c > 191) && (c < 224)) { + c2 = utftext.charCodeAt(i+1); + string += String.fromCharCode(((c & 31) << 6) | (c2 & 63)); + i += 2; + } + else { + c2 = utftext.charCodeAt(i+1); + c3 = utftext.charCodeAt(i+2); + string += String.fromCharCode(((c & 15) << 12) | ((c2 & 63) << 6) | (c3 & 63)); + i += 3; + } + } + + return string; + } + + return output; + } + + function transition (div_old, div_new, type, callBack) { + var width = $(div_old).width(); + var height = $(div_old).height(); + var time = 0.5; + + if (!div_old || !div_new) { + console.log('ERROR: Cannot do transition when one of the divs is null'); + return; + } + + div_old.parentNode.style.cssText += cross('perspective', '700px') +'; overflow: hidden;'; + div_old.style.cssText += '; position: absolute; z-index: 1019; '+ cross('backface-visibility', 'hidden'); + div_new.style.cssText += '; position: absolute; z-index: 1020; '+ cross('backface-visibility', 'hidden'); + + switch (type) { + case 'slide-left': + // init divs + div_old.style.cssText += 'overflow: hidden; '+ cross('transform', 'translate3d(0, 0, 0)', 'translate(0, 0)'); + div_new.style.cssText += 'overflow: hidden; '+ cross('transform', 'translate3d('+ width + 'px, 0, 0)', 'translate('+ width +'px, 0)'); + $(div_new).show(); + // -- need a timing function because otherwise not working + window.setTimeout(function() { + div_new.style.cssText += cross('transition', time+'s') +';'+ cross('transform', 'translate3d(0, 0, 0)', 'translate(0, 0)'); + div_old.style.cssText += cross('transition', time+'s') +';'+ cross('transform', 'translate3d(-'+ width +'px, 0, 0)', 'translate(-'+ width +'px, 0)'); + }, 1); + break; + + case 'slide-right': + // init divs + div_old.style.cssText += 'overflow: hidden; '+ cross('transform', 'translate3d(0, 0, 0)', 'translate(0, 0)'); + div_new.style.cssText += 'overflow: hidden; '+ cross('transform', 'translate3d(-'+ width +'px, 0, 0)', 'translate(-'+ width +'px, 0)'); + $(div_new).show(); + // -- need a timing function because otherwise not working + window.setTimeout(function() { + div_new.style.cssText += cross('transition', time+'s') +'; '+ cross('transform', 'translate3d(0px, 0, 0)', 'translate(0px, 0)'); + div_old.style.cssText += cross('transition', time+'s') +'; '+ cross('transform', 'translate3d('+ width +'px, 0, 0)', 'translate('+ width +'px, 0)'); + }, 1); + break; + + case 'slide-down': + // init divs + div_old.style.cssText += 'overflow: hidden; z-index: 1; '+ cross('transform', 'translate3d(0, 0, 0)', 'translate(0, 0)'); + div_new.style.cssText += 'overflow: hidden; z-index: 0; '+ cross('transform', 'translate3d(0, 0, 0)', 'translate(0, 0)'); + $(div_new).show(); + // -- need a timing function because otherwise not working + window.setTimeout(function() { + div_new.style.cssText += cross('transition', time+'s') +'; '+ cross('transform', 'translate3d(0, 0, 0)', 'translate(0, 0)'); + div_old.style.cssText += cross('transition', time+'s') +'; '+ cross('transform', 'translate3d(0, '+ height +'px, 0)', 'translate(0, '+ height +'px)'); + }, 1); + break; + + case 'slide-up': + // init divs + div_old.style.cssText += 'overflow: hidden; '+ cross('transform', 'translate3d(0, 0, 0)', 'translate(0, 0)'); + div_new.style.cssText += 'overflow: hidden; '+ cross('transform', 'translate3d(0, '+ height +'px, 0)', 'translate(0, '+ height +'px)'); + $(div_new).show(); + // -- need a timing function because otherwise not working + window.setTimeout(function() { + div_new.style.cssText += cross('transition', time+'s') +'; '+ cross('transform', 'translate3d(0, 0, 0)', 'translate(0, 0)'); + div_old.style.cssText += cross('transition', time+'s') +'; '+ cross('transform', 'translate3d(0, 0, 0)', 'translate(0, 0)'); + }, 1); + break; + + case 'flip-left': + // init divs + div_old.style.cssText += 'overflow: hidden; '+ cross('transform', 'rotateY(0deg)'); + div_new.style.cssText += 'overflow: hidden; '+ cross('transform', 'rotateY(-180deg)'); + $(div_new).show(); + // -- need a timing function because otherwise not working + window.setTimeout(function() { + div_new.style.cssText += cross('transition', time+'s') +'; '+ cross('transform', 'rotateY(0deg)'); + div_old.style.cssText += cross('transition', time+'s') +'; '+ cross('transform', 'rotateY(180deg)'); + }, 1); + break; + + case 'flip-right': + // init divs + div_old.style.cssText += 'overflow: hidden; '+ cross('transform', 'rotateY(0deg)'); + div_new.style.cssText += 'overflow: hidden; '+ cross('transform', 'rotateY(180deg)'); + $(div_new).show(); + // -- need a timing function because otherwise not working + window.setTimeout(function() { + div_new.style.cssText += cross('transition', time+'s') +'; '+ cross('transform', 'rotateY(0deg)'); + div_old.style.cssText += cross('transition', time+'s') +'; '+ cross('transform', 'rotateY(-180deg)'); + }, 1); + break; + + case 'flip-down': + // init divs + div_old.style.cssText += 'overflow: hidden; '+ cross('transform', 'rotateX(0deg)'); + div_new.style.cssText += 'overflow: hidden; '+ cross('transform', 'rotateX(180deg)'); + $(div_new).show(); + // -- need a timing function because otherwise not working + window.setTimeout(function() { + div_new.style.cssText += cross('transition', time+'s') +'; '+ cross('transform', 'rotateX(0deg)'); + div_old.style.cssText += cross('transition', time+'s') +'; '+ cross('transform', 'rotateX(-180deg)'); + }, 1); + break; + + case 'flip-up': + // init divs + div_old.style.cssText += 'overflow: hidden; '+ cross('transform', 'rotateX(0deg)'); + div_new.style.cssText += 'overflow: hidden; '+ cross('transform', 'rotateX(-180deg)'); + $(div_new).show(); + // -- need a timing function because otherwise not working + window.setTimeout(function() { + div_new.style.cssText += cross('transition', time+'s') +'; '+ cross('transform', 'rotateX(0deg)'); + div_old.style.cssText += cross('transition', time+'s') +'; '+ cross('transform', 'rotateX(180deg)'); + }, 1); + break; + + case 'pop-in': + // init divs + div_old.style.cssText += 'overflow: hidden; '+ cross('transform', 'translate3d(0, 0, 0)', 'translate(0, 0)'); + div_new.style.cssText += 'overflow: hidden; '+ cross('transform', 'translate3d(0, 0, 0)', 'translate(0, 0)') + '; '+ cross('transform', 'scale(.8)') + '; opacity: 0;'; + $(div_new).show(); + // -- need a timing function because otherwise not working + window.setTimeout(function() { + div_new.style.cssText += cross('transition', time+'s') +'; '+ cross('transform', 'scale(1)') +'; opacity: 1;'; + div_old.style.cssText += cross('transition', time+'s') +';'; + }, 1); + break; + + case 'pop-out': + // init divs + div_old.style.cssText += 'overflow: hidden; '+ cross('transform', 'translate3d(0, 0, 0)', 'translate(0, 0)') +'; '+ cross('transform', 'scale(1)') +'; opacity: 1;'; + div_new.style.cssText += 'overflow: hidden; '+ cross('transform', 'translate3d(0, 0, 0)', 'translate(0, 0)') +'; opacity: 0;'; + $(div_new).show(); + // -- need a timing function because otherwise not working + window.setTimeout(function() { + div_new.style.cssText += cross('transition', time+'s') +'; opacity: 1;'; + div_old.style.cssText += cross('transition', time+'s') +'; '+ cross('transform', 'scale(1.7)') +'; opacity: 0;'; + }, 1); + break; + + default: + // init divs + div_old.style.cssText += 'overflow: hidden; '+ cross('transform', 'translate3d(0, 0, 0)', 'translate(0, 0)'); + div_new.style.cssText += 'overflow: hidden; '+ cross('transform', 'translate3d(0, 0, 0)', 'translate(0, 0)') +'; opacity: 0;'; + $(div_new).show(); + // -- need a timing function because otherwise not working + window.setTimeout(function() { + div_new.style.cssText += cross('transition', time +'s') +'; opacity: 1;'; + div_old.style.cssText += cross('transition', time +'s'); + }, 1); + break; + } + + setTimeout(function () { + if (type === 'slide-down') { + $(div_old).css('z-index', '1019'); + $(div_new).css('z-index', '1020'); + } + if (div_new) { + $(div_new).css({ + 'opacity': '1', + '-webkit-transition': '', + '-moz-transition': '', + '-ms-transition': '', + '-o-transition': '', + '-webkit-transform': '', + '-moz-transform': '', + '-ms-transform': '', + '-o-transform': '', + '-webkit-backface-visibility': '', + '-moz-backface-visibility': '', + '-ms-backface-visibility': '', + '-o-backface-visibility': '' + }); + } + if (div_old) { + $(div_old).css({ + 'opacity': '1', + '-webkit-transition': '', + '-moz-transition': '', + '-ms-transition': '', + '-o-transition': '', + '-webkit-transform': '', + '-moz-transform': '', + '-ms-transform': '', + '-o-transform': '', + '-webkit-backface-visibility': '', + '-moz-backface-visibility': '', + '-ms-backface-visibility': '', + '-o-backface-visibility': '' + }); + if (div_old.parentNode) $(div_old.parentNode).css({ + '-webkit-perspective': '', + '-moz-perspective': '', + '-ms-perspective': '', + '-o-perspective': '' + }); + } + if (typeof callBack === 'function') callBack(); + }, time * 1000); + + function cross(property, value, none_webkit_value) { + var isWebkit=!!window.webkitURL; // jQuery no longer supports $.browser - RR + if (!isWebkit && typeof none_webkit_value !== 'undefined') value = none_webkit_value; + return ';'+ property +': '+ value +'; -webkit-'+ property +': '+ value +'; -moz-'+ property +': '+ value +'; '+ + '-ms-'+ property +': '+ value +'; -o-'+ property +': '+ value +';'; + } + } + + function lock (box, msg, spinner) { + var options = {}; + if (typeof msg === 'object') { + options = msg; + } else { + options.msg = msg; + options.spinner = spinner; + } + if (!options.msg && options.msg !== 0) options.msg = ''; + w2utils.unlock(box); + $(box).prepend( + '<div class="w2ui-lock"></div>'+ + '<div class="w2ui-lock-msg"></div>' + ); + var $lock = $(box).find('.w2ui-lock'); + var mess = $(box).find('.w2ui-lock-msg'); + if (!options.msg) mess.css({ 'background-color': 'transparent', 'border': '0px' }); + if (options.spinner === true) options.msg = '<div class="w2ui-spinner" '+ (!options.msg ? 'style="width: 35px; height: 35px"' : '') +'></div>' + options.msg; + if (options.opacity != null) $lock.css('opacity', options.opacity); + if (typeof $lock.fadeIn == 'function') { + $lock.fadeIn(200); + mess.html(options.msg).fadeIn(200); + } else { + $lock.show(); + mess.html(options.msg).show(0); + } + // hide all tags (do not hide overlays as the form can be in overlay) + $().w2tag(); + } + + function unlock (box) { + $(box).find('.w2ui-lock').remove(); + $(box).find('.w2ui-lock-msg').remove(); + } + + function getSize (el, type) { + var $el = $(el); + var bwidth = { + left : parseInt($el.css('border-left-width')) || 0, + right : parseInt($el.css('border-right-width')) || 0, + top : parseInt($el.css('border-top-width')) || 0, + bottom : parseInt($el.css('border-bottom-width')) || 0 + }; + var mwidth = { + left : parseInt($el.css('margin-left')) || 0, + right : parseInt($el.css('margin-right')) || 0, + top : parseInt($el.css('margin-top')) || 0, + bottom : parseInt($el.css('margin-bottom')) || 0 + }; + var pwidth = { + left : parseInt($el.css('padding-left')) || 0, + right : parseInt($el.css('padding-right')) || 0, + top : parseInt($el.css('padding-top')) || 0, + bottom : parseInt($el.css('padding-bottom')) || 0 + }; + switch (type) { + case 'top' : return bwidth.top + mwidth.top + pwidth.top; + case 'bottom' : return bwidth.bottom + mwidth.bottom + pwidth.bottom; + case 'left' : return bwidth.left + mwidth.left + pwidth.left; + case 'right' : return bwidth.right + mwidth.right + pwidth.right; + case 'width' : return bwidth.left + bwidth.right + mwidth.left + mwidth.right + pwidth.left + pwidth.right + parseInt($el.width()); + case 'height' : return bwidth.top + bwidth.bottom + mwidth.top + mwidth.bottom + pwidth.top + pwidth.bottom + parseInt($el.height()); + case '+width' : return bwidth.left + bwidth.right + mwidth.left + mwidth.right + pwidth.left + pwidth.right; + case '+height' : return bwidth.top + bwidth.bottom + mwidth.top + mwidth.bottom + pwidth.top + pwidth.bottom; + } + return 0; + } + + function lang (phrase) { + var translation = this.settings.phrases[phrase]; + if (translation == null) return phrase; else return translation; + } + + function locale (locale) { + if (!locale) locale = 'en-us'; + if (locale.length === 5) locale = 'locale/'+ locale +'.json'; + // load from the file + $.ajax({ + url : locale, + type : "GET", + dataType : "JSON", + async : false, + cache : false, + success : function (data, status, xhr) { + w2utils.settings = $.extend(true, w2utils.settings, data); + // apply translation to some prototype functions + var p = w2obj.grid.prototype; + for (var b in p.buttons) { + p.buttons[b].caption = w2utils.lang(p.buttons[b].caption); + p.buttons[b].hint = w2utils.lang(p.buttons[b].hint); + } + p.msgDelete = w2utils.lang(p.msgDelete); + p.msgNotJSON = w2utils.lang(p.msgNotJSON); + p.msgRefresh = w2utils.lang(p.msgRefresh); + }, + error : function (xhr, status, msg) { + console.log('ERROR: Cannot load locale '+ locale); + } + }); + } + + function scrollBarSize () { + if (tmp.scrollBarSize) return tmp.scrollBarSize; + var html = + '<div id="_scrollbar_width" style="position: absolute; top: -300px; width: 100px; height: 100px; overflow-y: scroll;">'+ + ' <div style="height: 120px">1</div>'+ + '</div>'; + $('body').append(html); + tmp.scrollBarSize = 100 - $('#_scrollbar_width > div').width(); + $('#_scrollbar_width').remove(); + if (String(navigator.userAgent).indexOf('MSIE') >= 0) tmp.scrollBarSize = tmp.scrollBarSize / 2; // need this for IE9+ + return tmp.scrollBarSize; + } + + + function checkName (params, component) { // was w2checkNameParam + if (!params || typeof params.name === 'undefined') { + console.log('ERROR: The parameter "name" is required but not supplied in $().'+ component +'().'); + return false; + } + if (typeof w2ui[params.name] !== 'undefined') { + console.log('ERROR: The parameter "name" is not unique. There are other objects already created with the same name (obj: '+ params.name +').'); + return false; + } + if (!w2utils.isAlphaNumeric(params.name)) { + console.log('ERROR: The parameter "name" has to be alpha-numeric (a-z, 0-9, dash and underscore). '); + return false; + } + return true; + } + + function checkUniqueId (id, items, itemsDecription, objName) { // was w2checkUniqueId + if (!$.isArray(items)) items = [items]; + for (var i = 0; i < items.length; i++) { + if (items[i].id === id) { + console.log('ERROR: The parameter "id='+ id +'" is not unique within the current '+ itemsDecription +'. (obj: '+ objName +')'); + return false; + } + } + return true; + } + + function parseRoute(route) { + var keys = []; + var path = route + .replace(/\/\(/g, '(?:/') + .replace(/\+/g, '__plus__') + .replace(/(\/)?(\.)?:(\w+)(?:(\(.*?\)))?(\?)?/g, function(_, slash, format, key, capture, optional) { + keys.push({ name: key, optional: !! optional }); + slash = slash || ''; + return '' + (optional ? '' : slash) + '(?:' + (optional ? slash : '') + (format || '') + (capture || (format && '([^/.]+?)' || '([^/]+?)')) + ')' + (optional || ''); + }) + .replace(/([\/.])/g, '\\$1') + .replace(/__plus__/g, '(.+)') + .replace(/\*/g, '(.*)'); + return { + path : new RegExp('^' + path + '$', 'i'), + keys : keys + }; + } +})(); + +/*********************************************************** +* Generic Event Object +* --- This object is reused across all other +* --- widgets in w2ui. +* +*********************************************************/ + +w2utils.event = { + + on: function (eventData, handler) { + if (!$.isPlainObject(eventData)) eventData = { type: eventData }; + eventData = $.extend({ type: null, execute: 'before', target: null, onComplete: null }, eventData); + + if (!eventData.type) { console.log('ERROR: You must specify event type when calling .on() method of '+ this.name); return; } + if (!handler) { console.log('ERROR: You must specify event handler function when calling .on() method of '+ this.name); return; } + if (!$.isArray(this.handlers)) this.handlers = []; + this.handlers.push({ event: eventData, handler: handler }); + }, + + off: function (eventData, handler) { + if (!$.isPlainObject(eventData)) eventData = { type: eventData }; + eventData = $.extend({}, { type: null, execute: 'before', target: null, onComplete: null }, eventData); + + if (!eventData.type) { console.log('ERROR: You must specify event type when calling .off() method of '+ this.name); return; } + if (!handler) { handler = null; } + // remove handlers + var newHandlers = []; + for (var h = 0, len = this.handlers.length; h < len; h++) { + var t = this.handlers[h]; + if ((t.event.type === eventData.type || eventData.type === '*') && + (t.event.target === eventData.target || eventData.target === null) && + (t.handler === handler || handler === null)) + { + // match + } else { + newHandlers.push(t); + } + } + this.handlers = newHandlers; + }, + + trigger: function (eventData) { + var eventData = $.extend({ type: null, phase: 'before', target: null }, eventData, { + isStopped: false, isCancelled: false, + preventDefault : function () { this.isCancelled = true; }, + stopPropagation : function () { this.isStopped = true; } + }); + if (eventData.phase === 'before') eventData.onComplete = null; + var args, fun, tmp; + if (eventData.target == null) eventData.target = null; + if (!$.isArray(this.handlers)) this.handlers = []; + // process events in REVERSE order + for (var h = this.handlers.length-1; h >= 0; h--) { + var item = this.handlers[h]; + if ((item.event.type === eventData.type || item.event.type === '*') && + (item.event.target === eventData.target || item.event.target === null) && + (item.event.execute === eventData.phase || item.event.execute === '*' || item.event.phase === '*')) + { + eventData = $.extend({}, item.event, eventData); + // check handler arguments + args = []; + tmp = RegExp(/\((.*?)\)/).exec(item.handler); + if (tmp) args = tmp[1].split(/\s*,\s*/); + if (args.length === 2) { + item.handler.call(this, eventData.target, eventData); // old way for back compatibility + } else { + item.handler.call(this, eventData); // new way + } + if (eventData.isStopped === true || eventData.stop === true) return eventData; // back compatibility eventData.stop === true + } + } + // main object events + var funName = 'on' + eventData.type.substr(0,1).toUpperCase() + eventData.type.substr(1); + if (eventData.phase === 'before' && typeof this[funName] === 'function') { + fun = this[funName]; + // check handler arguments + args = []; + tmp = RegExp(/\((.*?)\)/).exec(fun); + if (tmp) args = tmp[1].split(/\s*,\s*/); + if (args.length === 2) { + fun.call(this, eventData.target, eventData); // old way for back compatibility + } else { + fun.call(this, eventData); // new way + } + if (eventData.isStopped === true || eventData.stop === true) return eventData; // back compatibility eventData.stop === true + } + // item object events + if (eventData.object != null && eventData.phase === 'before' && + typeof eventData.object[funName] === 'function') + { + fun = eventData.object[funName]; + // check handler arguments + args = []; + tmp = RegExp(/\((.*?)\)/).exec(fun); + if (tmp) args = tmp[1].split(/\s*,\s*/); + if (args.length === 2) { + fun.call(this, eventData.target, eventData); // old way for back compatibility + } else { + fun.call(this, eventData); // new way + } + if (eventData.isStopped === true || eventData.stop === true) return eventData; + } + // execute onComplete + if (eventData.phase === 'after' && typeof eventData.onComplete === 'function') eventData.onComplete.call(this, eventData); + + return eventData; + } +}; + +/*********************************************************** +* Common Keyboard Handler. Supported in +* - grid +* - sidebar +* - popup +* +*********************************************************/ + +w2utils.keyboard = (function (obj) { + // private scope + var w2ui_name = null; + + obj.active = active; + obj.clear = clear; + + init(); + return obj; + + function init() { + $(document).on('keydown', keydown); + $(document).on('mousedown', mousedown); + } + + function keydown (event) { + var tag = event.target.tagName; + if ($.inArray(tag, ['INPUT', 'SELECT', 'TEXTAREA']) !== -1) return; + if ($(event.target).prop('contenteditable') === 'true') return; + if (!w2ui_name) return; + // pass to appropriate widget + if (w2ui[w2ui_name] && typeof w2ui[w2ui_name].keydown === 'function') { + w2ui[w2ui_name].keydown.call(w2ui[w2ui_name], event); + } + } + + function mousedown (event) { + var tag = event.target.tagName; + var obj = $(event.target).parents('.w2ui-reset'); + if (obj.length > 0) { + var name = obj.attr('name'); + if (w2ui[name] && w2ui[name].keyboard) w2ui_name = name; + } + } + + function active (new_w2ui_name) { + if (typeof new_w2ui_name !== 'undefined') w2ui_name = new_w2ui_name; + return w2ui_name; + } + + function clear () { + w2ui_name = null; + } + +})({}); + +/*********************************************************** +* Commonly used plugins +* --- used primarily in grid and form +* +*********************************************************/ + +(function () { + + $.fn.w2render = function (name) { + if ($(this).length > 0) { + if (typeof name === 'string' && w2ui[name]) w2ui[name].render($(this)[0]); + if (typeof name === 'object') name.render($(this)[0]); + } + }; + + $.fn.w2destroy = function (name) { + if (!name && this.length > 0) name = this.attr('name'); + if (typeof name === 'string' && w2ui[name]) w2ui[name].destroy(); + if (typeof name === 'object') name.destroy(); + }; + + $.fn.w2marker = function (str) { + if (str === '' || str == null) { // remove marker + return $(this).each(function (index, el) { + el.innerHTML = el.innerHTML.replace(/\<span class=\"w2ui\-marker\"\>(.*)\<\/span\>/ig, '$1'); // unmark + }); + } else { // add marker + return $(this).each(function (index, el) { + if (typeof str === 'string') str = [str]; + el.innerHTML = el.innerHTML.replace(/\<span class=\"w2ui\-marker\"\>(.*)\<\/span\>/ig, '$1'); // unmark + for (var s in str) { + var tmp = str[s]; + if (typeof tmp !== 'string') tmp = String(tmp); + // escape regex special chars + tmp = tmp.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&").replace(/&/g, '&').replace(/</g, '>').replace(/>/g, '<'); + var regex = new RegExp(tmp + '(?!([^<]+)?>)', "gi"); // only outside tags + el.innerHTML = el.innerHTML.replace(regex, replaceValue); + } + function replaceValue(matched) { // mark new + return '<span class="w2ui-marker">' + matched + '</span>'; + } + }); + } + }; + + // -- w2tag - appears on the right side from element, there can be multiple on screen at a time + + $.fn.w2tag = function (text, options) { + if (!$.isPlainObject(options)) options = {}; + if (!$.isPlainObject(options.css)) options.css = {}; + if (typeof options['class'] === 'undefined') options['class'] = ''; + // remove all tags + if ($(this).length === 0) { + $('.w2ui-tag').each(function (index, elem) { + var opt = $(elem).data('options'); + if (opt == null) opt = {}; + $($(elem).data('taged-el')).removeClass( opt['class'] ); + clearInterval($(elem).data('timer')); + $(elem).remove(); + }); + return; + } + return $(this).each(function (index, el) { + // show or hide tag + var tagOrigID = el.id; + var tagID = w2utils.escapeId(el.id); + if (text === '' || text == null) { + $('#w2ui-tag-'+tagID).css('opacity', 0); + setTimeout(function () { + // remmove element + clearInterval($('#w2ui-tag-'+tagID).data('timer')); + $('#w2ui-tag-'+tagID).remove(); + }, 300); + } else { + // remove elements + clearInterval($('#w2ui-tag-'+tagID).data('timer')); + $('#w2ui-tag-'+tagID).remove(); + // insert + $('body').append( + '<div id="w2ui-tag-'+ tagOrigID +'" class="w2ui-tag '+ ($(el).parents('.w2ui-popup').length > 0 ? 'w2ui-tag-popup' : '') + + '" style=""></div>'); + + var timer = setInterval(function () { + // monitor if destroyed + if ($(el).length === 0 || ($(el).offset().left === 0 && $(el).offset().top === 0)) { + clearInterval($('#w2ui-tag-'+tagID).data('timer')); + tmp_hide(); + return; + } + // monitor if moved + if ($('#w2ui-tag-'+tagID).data('position') !== ($(el).offset().left + el.offsetWidth) + 'x' + $(el).offset().top) { + $('#w2ui-tag-'+tagID).css({ + '-webkit-transition' : '.2s', + '-moz-transition' : '.2s', + '-ms-transition' : '.2s', + '-o-transition' : '.2s', + left: ($(el).offset().left + el.offsetWidth) + 'px', + top: $(el).offset().top + 'px' + }).data('position', ($(el).offset().left + el.offsetWidth) + 'x' + $(el).offset().top); + } + }, 100); + setTimeout(function () { + if (!$(el).offset()) return; + $('#w2ui-tag-'+tagID).css({ + opacity: '1', + left: ($(el).offset().left + el.offsetWidth) + 'px', + top: $(el).offset().top + 'px' + }).html('<div style="margin-top: -2px 0px 0px -2px; white-space: nowrap;"> <div class="w2ui-tag-body">'+ text +'</div> </div>') + .data('text', text) + .data('taged-el', el) + .data('options', options) + .data('position', ($(el).offset().left + el.offsetWidth) + 'x' + $(el).offset().top) + .data('timer', timer); + $(el).off('keypress', tmp_hide).on('keypress', tmp_hide).off('change', tmp_hide).on('change', tmp_hide) + .css(options.css).addClass(options['class']); + if (typeof options.onShow === 'function') options.onShow(); + }, 1); + var originalCSS = ''; + if ($(el).length > 0) originalCSS = $(el)[0].style.cssText; + // bind event to hide it + function tmp_hide() { + $tag = $('#w2ui-tag-'+tagID); + if ($tag.length <= 0) return; + clearInterval($tag.data('timer')); + $tag.remove(); + $(el).off('keypress', tmp_hide).removeClass(options['class']); + if ($(el).length > 0) $(el)[0].style.cssText = originalCSS; + if (typeof options.onHide === 'function') options.onHide(); + } + } + }); + }; + + // w2overlay - appears under the element, there can be only one at a time + + $.fn.w2overlay = function (html, options) { + var obj = this; + var name = ''; + var defaults = { + name : null, // it not null, then allows multiple concurent overlays + html : '', // html text to display + align : 'none', // can be none, left, right, both + left : 0, // offset left + top : 0, // offset top + tipLeft : 30, // tip offset left + width : 0, // fixed width + height : 0, // fixed height + maxWidth : null, // max width if any + maxHeight : null, // max height if any + style : '', // additional style for main div + 'class' : '', // additional class name for main div + onShow : null, // event on show + onHide : null, // event on hide + openAbove : false, // show abover control + tmp : {} + }; + if (arguments.length == 1) { + if (typeof html == 'object') { + options = html; + } else { + options = { html: html }; + } + } + if (arguments.length == 2) options.html = html; + if (!$.isPlainObject(options)) options = {}; + options = $.extend({}, defaults, options); + if (options.name) name = '-' + options.name; + // if empty then hide + var tmp_hide; + if (this.length === 0 || options.html === '' || options.html == null) { + if ($('#w2ui-overlay'+ name).length > 0) { + tmp_hide = $('#w2ui-overlay'+ name)[0].hide; + if (typeof tmp_hide === 'function') tmp_hide(); + } else { + $('#w2ui-overlay'+ name).remove(); + } + return $(this); + } + if ($('#w2ui-overlay'+ name).length > 0) { + tmp_hide = $('#w2ui-overlay'+ name)[0].hide; + $(document).off('click', tmp_hide); + if (typeof tmp_hide === 'function') tmp_hide(); + } + $('body').append( + '<div id="w2ui-overlay'+ name +'" style="display: none"'+ + ' class="w2ui-reset w2ui-overlay '+ ($(this).parents('.w2ui-popup, .w2ui-overlay-popup').length > 0 ? 'w2ui-overlay-popup' : '') +'">'+ + ' <style></style>'+ + ' <div style="'+ options.style +'" class="'+ options['class'] +'"></div>'+ + '</div>' + ); + // init + var div1 = $('#w2ui-overlay'+ name); + var div2 = div1.find(' > div'); + div2.html(options.html); + // pick bg color of first div + var bc = div2.css('background-color'); + if (bc != null && bc !== 'rgba(0, 0, 0, 0)' && bc !== 'transparent') div1.css('background-color', bc); + + div1.data('element', obj.length > 0 ? obj[0] : null) + .data('options', options) + .data('position', $(obj).offset().left + 'x' + $(obj).offset().top) + .fadeIn('fast').on('mousedown', function (event) { + $('#w2ui-overlay'+ name).data('keepOpen', true); + if (['INPUT', 'TEXTAREA', 'SELECT'].indexOf(event.target.tagName) === -1) event.preventDefault(); + }); + div1[0].hide = hide; + div1[0].resize = resize; + + // need time to display + resize(); + setTimeout(function () { + resize(); + $(document).off('click', hide).on('click', hide); + if (typeof options.onShow === 'function') options.onShow(); + }, 10); + + monitor(); + return $(this); + + // monitor position + function monitor() { + var tmp = $('#w2ui-overlay'+ name); + if (tmp.data('element') !== obj[0]) return; // it if it different overlay + if (tmp.length === 0) return; + var pos = $(obj).offset().left + 'x' + $(obj).offset().top; + if (tmp.data('position') !== pos) { + hide(); + } else { + setTimeout(monitor, 250); + } + } + + // click anywhere else hides the drop down + function hide () { + var div1 = $('#w2ui-overlay'+ name); + if (div1.data('keepOpen') === true) { + div1.removeData('keepOpen'); + return; + } + var result; + if (typeof options.onHide === 'function') result = options.onHide(); + if (result === false) return; + div1.remove(); + $(document).off('click', hide); + clearInterval(div1.data('timer')); + } + + function resize () { + var div1 = $('#w2ui-overlay'+ name); + var div2 = div1.find(' > div'); + // if goes over the screen, limit height and width + if (div1.length > 0) { + div2.height('auto').width('auto'); + // width/height + var overflowX = false; + var overflowY = false; + var h = div2.height(); + var w = div2.width(); + if (options.width && options.width < w) w = options.width; + if (w < 30) w = 30; + // if content of specific height + if (options.tmp.contentHeight) { + h = options.tmp.contentHeight; + div2.height(h); + setTimeout(function () { + if (div2.height() > div2.find('div.menu > table').height()) { + div2.find('div.menu').css('overflow-y', 'hidden'); + } + }, 1); + setTimeout(function () { div2.find('div.menu').css('overflow-y', 'auto'); }, 10); + } + if (options.tmp.contentWidth) { + w = options.tmp.contentWidth; + div2.width(w); + setTimeout(function () { + if (div2.width() > div2.find('div.menu > table').width()) { + div2.find('div.menu').css('overflow-x', 'hidden'); + } + }, 1); + setTimeout(function () { div2.find('div.menu').css('overflow-y', 'auto'); }, 10); + } + // alignment + switch (options.align) { + case 'both': + options.left = 17; + if (options.width === 0) options.width = w2utils.getSize($(obj), 'width'); + break; + case 'left': + options.left = 17; + break; + case 'right': + options.tipLeft = w - 45; + options.left = w2utils.getSize($(obj), 'width') - w + 10; + break; + } + // adjust position + var tmp = (w - 17) / 2; + var boxLeft = options.left; + var boxWidth = options.width; + var tipLeft = options.tipLeft; + if (w === 30 && !boxWidth) boxWidth = 30; else boxWidth = (options.width ? options.width : 'auto'); + if (tmp < 25) { + boxLeft = 25 - tmp; + tipLeft = Math.floor(tmp); + } + // Y coord + div1.css({ + top : (obj.offset().top + w2utils.getSize(obj, 'height') + options.top + 7) + 'px', + left : ((obj.offset().left > 25 ? obj.offset().left : 25) + boxLeft) + 'px', + 'min-width' : boxWidth, + 'min-height': (options.height ? options.height : 'auto') + }); + // $(window).height() - has a problem in FF20 + var maxHeight = window.innerHeight + $(document).scrollTop() - div2.offset().top - 7; + var maxWidth = window.innerWidth + $(document).scrollLeft() - div2.offset().left - 7; + if ((maxHeight > -50 && maxHeight < 210) || options.openAbove === true) { + // show on top + maxHeight = div2.offset().top - $(document).scrollTop() - 7; + if (options.maxHeight && maxHeight > options.maxHeight) maxHeight = options.maxHeight; + if (h > maxHeight) { + overflowY = true; + div2.height(maxHeight).width(w).css({ 'overflow-y': 'auto' }); + h = maxHeight; + } + div1.css('top', ($(obj).offset().top - h - 24 + options.top) + 'px'); + div1.find('>style').html( + '#w2ui-overlay'+ name +':before { display: none; margin-left: '+ parseInt(tipLeft) +'px; }'+ + '#w2ui-overlay'+ name +':after { display: block; margin-left: '+ parseInt(tipLeft) +'px; }' + ); + } else { + // show under + if (options.maxHeight && maxHeight > options.maxHeight) maxHeight = options.maxHeight; + if (h > maxHeight) { + overflowY = true; + div2.height(maxHeight).width(w).css({ 'overflow-y': 'auto' }); + } + div1.find('>style').html( + '#w2ui-overlay'+ name +':before { display: block; margin-left: '+ parseInt(tipLeft) +'px; }'+ + '#w2ui-overlay'+ name +':after { display: none; margin-left: '+ parseInt(tipLeft) +'px; }' + ); + } + // check width + w = div2.width(); + maxWidth = window.innerWidth + $(document).scrollLeft() - div2.offset().left - 7; + if (options.maxWidth && maxWidth > options.maxWidth) maxWidth = options.maxWidth; + if (w > maxWidth && options.align !== 'both') { + options.align = 'right'; + setTimeout(function () { resize(); }, 1); + } + // check scroll bar + if (overflowY && overflowX) div2.width(w + w2utils.scrollBarSize() + 2); + } + } + }; + + $.fn.w2menu = function (menu, options) { + /* + ITEM STRUCTURE + item : { + id : null, + text : '', + style : '', + img : '', + icon : '', + count : '', + hidden : false, + disabled : false + ... + } + */ + var defaults = { + index : null, // current selected + items : [], + render : null, + msgNoItems : 'No items', + onSelect : null, + tmp : {} + }; + var obj = this; + var name = ''; + if (menu === 'refresh') { + // if not show - call blur + if ($('#w2ui-overlay'+ name).length > 0) { + options = $.extend($.fn.w2menuOptions, options); + var scrTop = $('#w2ui-overlay'+ name +' div.menu').scrollTop(); + $('#w2ui-overlay'+ name +' div.menu').html(getMenuHTML()); + $('#w2ui-overlay'+ name +' div.menu').scrollTop(scrTop); + mresize(); + } else { + $(this).w2menu(options); + } + } else { + if (arguments.length === 1) options = menu; else options.items = menu; + if (typeof options !== 'object') options = {}; + options = $.extend({}, defaults, options); + $.fn.w2menuOptions = options; + if (options.name) name = '-' + options.name; + if (typeof options.select === 'function' && typeof options.onSelect !== 'function') options.onSelect = options.select; + if (typeof options.onRender === 'function' && typeof options.render !== 'function') options.render = options.onRender; + // since only one overlay can exist at a time + $.fn.w2menuHandler = function (event, index) { + if (typeof options.onSelect === 'function') { + // need time so that menu first hides + setTimeout(function () { + options.onSelect({ + index : index, + item : options.items[index], + originalEvent: event + }); + }, 10); + } + setTimeout(function () { $(document).click(); }, 50); + }; + var html = ''; + if (options.search) { + html += + '<div style="position: absolute; top: 0px; height: 40px; left: 0px; right: 0px; border-bottom: 1px solid silver; background-color: #ECECEC; padding: 8px 5px;">'+ + ' <div class="w2ui-icon icon-search" style="position: absolute; margin-top: 4px; margin-left: 6px; width: 11px; background-position: left !important;"></div>'+ + ' <input id="menu-search" type="text" style="width: 100%; outline: none; padding-left: 20px;" onclick="event.stopPropagation();">'+ + '</div>'; + options.style += ';background-color: #ECECEC'; + options.index = 0; + for (var i in options.items) options.items[i].hidden = false; + } + html += '<div class="menu" style="position: absolute; top: '+ (options.search ? 40 : 0) + 'px; bottom: 0px; width: 100%; overflow: auto;">' + + getMenuHTML() + + '</div>'; + var ret = $(this).w2overlay(html, options); + setTimeout(function () { + $('#w2ui-overlay'+ name +' #menu-search') + .on('keyup', change) + .on('keydown', function (event) { + // cancel tab key + if (event.keyCode === 9) { event.stopPropagation(); event.preventDefault(); } + }); + if (options.search) { + if (['text', 'password'].indexOf($(obj)[0].type) != -1 || $(obj)[0].tagName == 'texarea') return; + $('#w2ui-overlay'+ name +' #menu-search').focus(); + } + }, 200); + mresize(); + return ret; + } + + function mresize() { + setTimeout(function () { + // show selected + $('#w2ui-overlay'+ name +' tr.w2ui-selected').removeClass('w2ui-selected'); + var cur = $('#w2ui-overlay'+ name +' tr[index='+ options.index +']'); + var scrTop = $('#w2ui-overlay'+ name +' div.menu').scrollTop(); + cur.addClass('w2ui-selected'); + if (options.tmp) options.tmp.contentHeight = $('#w2ui-overlay'+ name +' table').height() + (options.search ? 50 : 10); + if (options.tmp) options.tmp.contentWidth = $('#w2ui-overlay'+ name +' table').width(); + if ($('#w2ui-overlay'+ name).length > 0) $('#w2ui-overlay'+ name)[0].resize(); + // scroll into view + if (cur.length > 0) { + var top = cur[0].offsetTop - 5; // 5 is margin top + var el = $('#w2ui-overlay'+ name +' div.menu'); + var height = el.height(); + $('#w2ui-overlay'+ name +' div.menu').scrollTop(scrTop); + if (top < scrTop || top + cur.height() > scrTop + height) { + $('#w2ui-overlay'+ name +' div.menu').animate({ 'scrollTop': top - (height - cur.height() * 2) / 2 }, 200, 'linear'); + } + } + }, 1); + } + + function change(event) { + var search = this.value; + var key = event.keyCode; + var cancel = false; + switch (key) { + case 13: // enter + $('#w2ui-overlay'+ name).remove(); + $.fn.w2menuHandler(event, options.index); + break; + case 9: // tab + case 27: // escape + $('#w2ui-overlay'+ name).remove(); + $.fn.w2menuHandler(event, -1); + break; + case 38: // up + options.index = w2utils.isInt(options.index) ? parseInt(options.index) : 0; + options.index--; + while (options.index > 0 && options.items[options.index].hidden) options.index--; + if (options.index === 0 && options.items[options.index].hidden) { + while (options.items[options.index] && options.items[options.index].hidden) options.index++; + } + if (options.index < 0) options.index = 0; + cancel = true; + break; + case 40: // down + options.index = w2utils.isInt(options.index) ? parseInt(options.index) : 0; + options.index++; + while (options.index < options.items.length-1 && options.items[options.index].hidden) options.index++; + if (options.index === options.items.length-1 && options.items[options.index].hidden) { + while (options.items[options.index] && options.items[options.index].hidden) options.index--; + } + if (options.index >= options.items.length) options.index = options.items.length - 1; + cancel = true; + break; + } + // filter + if (!cancel) { + var shown = 0; + for (var i in options.items) { + var item = options.items[i]; + var prefix = ''; + var suffix = ''; + if (['is', 'begins with'].indexOf(options.match) !== -1) prefix = '^'; + if (['is', 'ends with'].indexOf(options.match) !== -1) suffix = '$'; + try { + var re = new RegExp(prefix + search + suffix, 'i'); + if (re.test(item.text) || item.text === '...') item.hidden = false; else item.hidden = true; + } catch (e) {} + // do not show selected items + if (obj.type === 'enum' && $.inArray(item.id, ids) !== -1) item.hidden = true; + if (item.hidden !== true) shown++; + } + options.index = 0; + while (options.index < options.items.length-1 && options.items[options.index].hidden) options.index++; + if (shown <= 0) options.index = -1; + } + $(obj).w2menu('refresh', options); + mresize(); + } + + function getMenuHTML () { + if (options.spinner) { + return '<table class="w2ui-drop-menu"><tr><td style="padding: 5px 10px 10px 10px; text-align: center">'+ + ' <div class="w2ui-spinner" style="width: 18px; height: 18px; position: relative; top: 5px;"></div> '+ + ' <div style="display: inline-block; padding: 3px; color: #999;"> Loading...</div>'+ + '</td></tr></table>'; + } + var count = 0; + var menu_html = '<table cellspacing="0" cellpadding="0" class="w2ui-drop-menu">'; + var img = null, icon = null; + for (var f = 0; f < options.items.length; f++) { + var mitem = options.items[f]; + if (typeof mitem === 'string') { + mitem = { id: mitem, text: mitem }; + } else { + if (mitem.text != null && mitem.id == null) mitem.id = mitem.text; + if (mitem.text == null && mitem.id != null) mitem.text = mitem.id; + if (mitem.caption != null) mitem.text = mitem.caption; + img = mitem.img; + icon = mitem.icon; + if (img == null) img = null; + if (icon == null) icon = null; + } + if (mitem.hidden !== true) { + var imgd = ''; + var txt = mitem.text; + if (typeof options.render === 'function') txt = options.render(mitem, options); + if (img) imgd = '<td class="menu-icon"><div class="w2ui-tb-image w2ui-icon '+ img +'"></div></td>'; + if (icon) imgd = '<td class="menu-icon" align="center"><span class="w2ui-icon '+ icon +'"></span></td>'; + // render only if non-empty + if (typeof txt !== 'undefined' && txt !== '' && !(/^-+$/.test(txt))) { + var bg = (count % 2 === 0 ? 'w2ui-item-even' : 'w2ui-item-odd'); + if (options.altRows !== true) bg = ''; + var colspan = 1; + if (imgd == '') colspan++; + if (mitem.count == null) colspan++; + menu_html += + '<tr index="'+ f + '" style="'+ (mitem.style ? mitem.style : '') +'" '+ + ' class="'+ bg +' '+ (options.index === f ? 'w2ui-selected' : '') + ' ' + (mitem.disabled === true ? 'w2ui-disabled' : '') +'"'+ + ' onmousedown="$(this).parent().find(\'tr\').removeClass(\'w2ui-selected\'); $(this).addClass(\'w2ui-selected\');"'+ + ' onclick="event.stopPropagation(); '+ + ' if ('+ (mitem.disabled === true ? 'true' : 'false') + ') return;'+ + ' $(\'#w2ui-overlay'+ name +'\').remove(); '+ + ' $.fn.w2menuHandler(event, \''+ f +'\');">'+ + imgd + + ' <td class="menu-text" colspan="'+ colspan +'">'+ txt +'</td>'+ + ' <td class="menu-count">'+ (mitem.count != null ? '<span>' + mitem.count + '</span>' : '') + '</td>' + + '</tr>'; + count++; + } else { + // horizontal line + menu_html += '<tr><td colspan="2" style="padding: 6px; pointer-events: none"><div style="border-top: 1px solid silver;"></div></td></tr>'; + } + } + options.items[f] = mitem; + } + if (count === 0) { + menu_html += '<tr><td style="padding: 13px; color: #999; text-align: center">'+ options.msgNoItems +'</div></td></tr>'; + } + menu_html += "</table>"; + return menu_html; + } + }; +})(); + + +/************************************************************************ +* Library: Web 2.0 UI for jQuery (using prototypical inheritance) +* - Following objects defined +* - w2grid - grid widget +* - $().w2grid - jQuery wrapper +* - Dependencies: jQuery, w2utils, w2toolbar, w2fields, w2alert, w2confirm +* +* == NICE TO HAVE == +* - frozen columns +* - add colspans +* - allow this.total to be unknown (-1) +* - column autosize based on largest content +* - easy bubbles in the grid +* - More than 2 layers of header groups +* - reorder columns/records +* - hidden searches could not be clearned by the user +* - problem with .set() and arrays, array get extended too, but should be replaced +* - move events into prototype +* - add grid.focus() +* - add showExtra, KickIn Infinite scroll when so many records +* - after edit stay on the same record option +* - allow render: function to be filters +* +************************************************************************/ + +(function () { + var w2grid = function(options) { + + // public properties + this.name = null; + this.box = null; // HTML element that hold this element + this.header = ''; + this.url = ''; + this.routeData = {}; // data for dynamic routes + this.columns = []; // { field, caption, size, attr, render, hidden, gridMinWidth, editable } + this.columnGroups = []; // { span: int, caption: 'string', master: true/false } + this.records = []; // { recid: int(requied), field1: 'value1', ... fieldN: 'valueN', style: 'string', editable: true/false, summary: true/false, changes: object } + this.summary = []; // arry of summary records, same structure as records array + this.searches = []; // { type, caption, field, inTag, outTag, hidden } + this.searchData = []; + this.sortData = []; + this.postData = {}; + this.toolbar = {}; // if not empty object; then it is toolbar object + + this.show = { + header : false, + toolbar : false, + footer : false, + columnHeaders : true, + lineNumbers : false, + expandColumn : false, + selectColumn : false, + emptyRecords : true, + toolbarReload : true, + toolbarColumns : true, + toolbarSearch : true, + toolbarAdd : false, + toolbarEdit : false, + toolbarDelete : false, + toolbarSave : false, + selectionBorder : true, + recordTitles : true, + skipRecords : true + }; + + this.autoLoad = true; // for infinite scroll + this.fixedBody = true; // if false; then grid grows with data + this.recordHeight = 24; + this.keyboard = true; + this.selectType = 'row'; // can be row|cell + this.multiSearch = true; + this.multiSelect = true; + this.multiSort = true; + this.reorderColumns = false; + this.reorderRows = false; + this.markSearch = true; + + this.total = 0; // server total + this.limit = 100; + this.offset = 0; // how many records to skip (for infinite scroll) when pulling from server + this.style = ''; + this.ranges = []; + this.menu = []; + this.method = null; // if defined, then overwrited ajax method + this.recid = null; + this.parser = null; + + // events + this.onAdd = null; + this.onEdit = null; + this.onRequest = null; // called on any server event + this.onLoad = null; + this.onDelete = null; + this.onDeleted = null; + this.onSubmit = null; + this.onSave = null; + this.onSelect = null; + this.onUnselect = null; + this.onClick = null; + this.onDblClick = null; + this.onContextMenu = null; + this.onMenuClick = null; // when context menu item selected + this.onColumnClick = null; + this.onColumnResize = null; + this.onSort = null; + this.onSearch = null; + this.onChange = null; // called when editable record is changed + this.onRestore = null; // called when editable record is restored + this.onExpand = null; + this.onCollapse = null; + this.onError = null; + this.onKeydown = null; + this.onToolbar = null; // all events from toolbar + this.onColumnOnOff = null; + this.onCopy = null; + this.onPaste = null; + this.onSelectionExtend = null; + this.onEditField = null; + this.onRender = null; + this.onRefresh = null; + this.onReload = null; + this.onResize = null; + this.onDestroy = null; + this.onStateSave = null; + this.onStateRestore = null; + + // internal + this.last = { + field : 'all', + caption : w2utils.lang('All Fields'), + logic : 'OR', + search : '', + searchIds : [], + selection : { + indexes : [], + columns : {} + }, + multi : false, + scrollTop : 0, + scrollLeft : 0, + sortData : null, + sortCount : 0, + xhr : null, + range_start : null, + range_end : null, + sel_ind : null, + sel_col : null, + sel_type : null, + edit_col : null + }; + + $.extend(true, this, w2obj.grid, options); + }; + + // ==================================================== + // -- Registers as a jQuery plugin + + $.fn.w2grid = function(method) { + if (typeof method === 'object' || !method ) { + // check name parameter + if (!w2utils.checkName(method, 'w2grid')) return; + // remember items + var columns = method.columns; + var columnGroups = method.columnGroups; + var records = method.records; + var searches = method.searches; + var searchData = method.searchData; + var sortData = method.sortData; + var postData = method.postData; + var toolbar = method.toolbar; + // extend items + var object = new w2grid(method); + $.extend(object, { postData: {}, records: [], columns: [], searches: [], toolbar: {}, sortData: [], searchData: [], handlers: [] }); + if (object.onExpand != null) object.show.expandColumn = true; + $.extend(true, object.toolbar, toolbar); + // reassign variables + for (var p in columns) object.columns[p] = $.extend(true, {}, columns[p]); + for (var p in columnGroups) object.columnGroups[p] = $.extend(true, {}, columnGroups[p]); + for (var p in searches) object.searches[p] = $.extend(true, {}, searches[p]); + for (var p in searchData) object.searchData[p] = $.extend(true, {}, searchData[p]); + for (var p in sortData) object.sortData[p] = $.extend(true, {}, sortData[p]); + object.postData = $.extend(true, {}, postData); + + // check if there are records without recid + for (var r in records) { + if (records[r].recid == null || typeof records[r].recid == 'undefined') { + console.log('ERROR: Cannot add records without recid. (obj: '+ object.name +')'); + return; + } + object.records[r] = $.extend(true, {}, records[r]); + } + // add searches + for (var c in object.columns) { + var col = object.columns[c]; + if (typeof col.searchable == 'undefined' || object.getSearch(col.field) != null) continue; + var stype = col.searchable; + var attr = ''; + if (col.searchable === true) { stype = 'text'; attr = 'size="20"'; } + object.addSearch({ field: col.field, caption: col.caption, type: stype, attr: attr }); + } + // init toolbar + object.initToolbar(); + // render if necessary + if ($(this).length !== 0) { + object.render($(this)[0]); + } + // register new object + w2ui[object.name] = object; + return object; + + } else if (w2ui[$(this).attr('name')]) { + var obj = w2ui[$(this).attr('name')]; + obj[method].apply(obj, Array.prototype.slice.call(arguments, 1)); + return this; + } else { + console.log('ERROR: Method ' + method + ' does not exist on jQuery.w2grid'); + } + } + + // ==================================================== + // -- Implementation of core functionality + + w2grid.prototype = { + // ---- + // properties that need to be in prototype + + msgDelete : 'Are you sure you want to delete selected records?', + msgNotJSON : 'Returned data is not in valid JSON format.', + msgAJAXerror : 'AJAX error. See console for more details.', + msgRefresh : 'Refreshing...', + + // for easy button overwrite + buttons: { + 'reload' : { type: 'button', id: 'w2ui-reload', icon: 'w2ui-icon-reload', hint: 'Reload data in the list' }, + 'columns' : { type: 'drop', id: 'w2ui-column-on-off', icon: 'w2ui-icon-columns', hint: 'Show/hide columns', arrow: false, html: '' }, + 'search' : { type: 'html', id: 'w2ui-search', + html: '<div class="w2ui-icon icon-search-down w2ui-search-down" title="'+ 'Select Search Field' +'" '+ + 'onclick="var obj = w2ui[$(this).parents(\'div.w2ui-grid\').attr(\'name\')]; obj.searchShowFields();"></div>' + }, + 'search-go': { type: 'check', id: 'w2ui-search-advanced', caption: 'Search...', hint: 'Open Search Fields' }, + 'add' : { type: 'button', id: 'w2ui-add', caption: 'Add New', hint: 'Add new record', icon: 'w2ui-icon-plus' }, + 'edit' : { type: 'button', id: 'w2ui-edit', caption: 'Edit', hint: 'Edit selected record', icon: 'w2ui-icon-pencil', disabled: true }, + 'delete' : { type: 'button', id: 'w2ui-delete', caption: 'Delete', hint: 'Delete selected records', icon: 'w2ui-icon-cross', disabled: true }, + 'save' : { type: 'button', id: 'w2ui-save', caption: 'Save', hint: 'Save changed records', icon: 'w2ui-icon-check' } + }, + + add: function (record) { + if (!$.isArray(record)) record = [record]; + var added = 0; + for (var o in record) { + if (!this.recid && typeof record[o].recid == 'undefined') record[o].recid = record[o][this.recid]; + if (record[o].recid == null || typeof record[o].recid == 'undefined') { + console.log('ERROR: Cannot add record without recid. (obj: '+ this.name +')'); + continue; + } + this.records.push(record[o]); + added++; + } + var url = (typeof this.url != 'object' ? this.url : this.url.get); + if (!url) { + this.total = this.records.length; + this.localSort(); + this.localSearch(); + } + this.refresh(); // ?? should it be reload? + return added; + }, + + find: function (obj, returnIndex) { + if (typeof obj == 'undefined' || obj == null) obj = {}; + var recs = []; + var hasDots = false; + // check if property is nested - needed for speed + for (var o in obj) if (String(o).indexOf('.') != -1) hasDots = true; + // look for an item + for (var i = 0; i < this.records.length; i++) { + var match = true; + for (var o in obj) { + var val = this.records[i][o]; + if (hasDots && String(o).indexOf('.') != -1) val = this.parseField(this.records[i], o); + if (obj[o] != val) match = false; + } + if (match && returnIndex !== true) recs.push(this.records[i].recid); + if (match && returnIndex === true) recs.push(i); + } + return recs; + }, + + set: function (recid, record, noRefresh) { // does not delete existing, but overrides on top of it + if (typeof recid == 'object') { + noRefresh = record; + record = recid; + recid = null; + } + // update all records + if (recid == null) { + for (var r in this.records) { + $.extend(true, this.records[r], record); // recid is the whole record + } + if (noRefresh !== true) this.refresh(); + } else { // find record to update + var ind = this.get(recid, true); + if (ind == null) return false; + $.extend(true, this.records[ind], record); + if (noRefresh !== true) this.refreshRow(recid); // refresh only that record + } + return true; + }, + + get: function (recid, returnIndex) { + for (var i = 0; i < this.records.length; i++) { + if (this.records[i].recid == recid) { + if (returnIndex === true) return i; else return this.records[i]; + } + } + return null; + }, + + remove: function () { + var removed = 0; + for (var a = 0; a < arguments.length; a++) { + for (var r = this.records.length-1; r >= 0; r--) { + if (this.records[r].recid == arguments[a]) { this.records.splice(r, 1); removed++; } + } + } + var url = (typeof this.url != 'object' ? this.url : this.url.get); + if (!url) { + this.localSort(); + this.localSearch(); + } + this.refresh(); + return removed; + }, + + addColumn: function (before, columns) { + var added = 0; + if (arguments.length == 1) { + columns = before; + before = this.columns.length; + } else { + if (typeof before == 'string') before = this.getColumn(before, true); + if (before === null) before = this.columns.length; + } + if (!$.isArray(columns)) columns = [columns]; + for (var o in columns) { + this.columns.splice(before, 0, columns[o]); + before++; + added++; + } + this.refresh(); + return added; + }, + + removeColumn: function () { + var removed = 0; + for (var a = 0; a < arguments.length; a++) { + for (var r = this.columns.length-1; r >= 0; r--) { + if (this.columns[r].field == arguments[a]) { this.columns.splice(r, 1); removed++; } + } + } + this.refresh(); + return removed; + }, + + getColumn: function (field, returnIndex) { + for (var i = 0; i < this.columns.length; i++) { + if (this.columns[i].field == field) { + if (returnIndex === true) return i; else return this.columns[i]; + } + } + return null; + }, + + toggleColumn: function () { + var effected = 0; + for (var a = 0; a < arguments.length; a++) { + for (var r = this.columns.length-1; r >= 0; r--) { + var col = this.columns[r]; + if (col.field == arguments[a]) { + col.hidden = !col.hidden; + effected++; + } + } + } + this.refresh(); + return effected; + }, + + showColumn: function () { + var shown = 0; + for (var a = 0; a < arguments.length; a++) { + for (var r = this.columns.length-1; r >= 0; r--) { + var col = this.columns[r]; + if (col.gridMinWidth) delete col.gridMinWidth; + if (col.field == arguments[a] && col.hidden !== false) { + col.hidden = false; + shown++; + } + } + } + this.refresh(); + return shown; + }, + + hideColumn: function () { + var hidden = 0; + for (var a = 0; a < arguments.length; a++) { + for (var r = this.columns.length-1; r >= 0; r--) { + var col = this.columns[r]; + if (col.field == arguments[a] && col.hidden !== true) { + col.hidden = true; + hidden++; + } + } + } + this.refresh(); + return hidden; + }, + + addSearch: function (before, search) { + var added = 0; + if (arguments.length == 1) { + search = before; + before = this.searches.length; + } else { + if (typeof before == 'string') before = this.getSearch(before, true); + if (before === null) before = this.searches.length; + } + if (!$.isArray(search)) search = [search]; + for (var o in search) { + this.searches.splice(before, 0, search[o]); + before++; + added++; + } + this.searchClose(); + return added; + }, + + removeSearch: function () { + var removed = 0; + for (var a = 0; a < arguments.length; a++) { + for (var r = this.searches.length-1; r >= 0; r--) { + if (this.searches[r].field == arguments[a]) { this.searches.splice(r, 1); removed++; } + } + } + this.searchClose(); + return removed; + }, + + getSearch: function (field, returnIndex) { + for (var i = 0; i < this.searches.length; i++) { + if (this.searches[i].field == field) { + if (returnIndex === true) return i; else return this.searches[i]; + } + } + return null; + }, + + toggleSearch: function () { + var effected = 0; + for (var a = 0; a < arguments.length; a++) { + for (var r = this.searches.length-1; r >= 0; r--) { + if (this.searches[r].field == arguments[a]) { + this.searches[r].hidden = !this.searches[r].hidden; + effected++; + } + } + } + this.searchClose(); + return effected; + }, + + showSearch: function () { + var shown = 0; + for (var a = 0; a < arguments.length; a++) { + for (var r = this.searches.length-1; r >= 0; r--) { + if (this.searches[r].field == arguments[a] && this.searches[r].hidden !== false) { + this.searches[r].hidden = false; + shown++; + } + } + } + this.searchClose(); + return shown; + }, + + hideSearch: function () { + var hidden = 0; + for (var a = 0; a < arguments.length; a++) { + for (var r = this.searches.length-1; r >= 0; r--) { + if (this.searches[r].field == arguments[a] && this.searches[r].hidden !== true) { + this.searches[r].hidden = true; + hidden++; + } + } + } + this.searchClose(); + return hidden; + }, + + getSearchData: function (field) { + for (var s in this.searchData) { + if (this.searchData[s].field == field) return this.searchData[s]; + } + return null; + }, + + localSort: function (silent) { + var url = (typeof this.url != 'object' ? this.url : this.url.get); + if (url) { + console.log('ERROR: grid.localSort can only be used on local data source, grid.url should be empty.'); + return; + } + if ($.isEmptyObject(this.sortData)) return; + var time = (new Date()).getTime(); + var obj = this; + // process date fields + obj.prepareData(); + // process sortData + for (var s in this.sortData) { + var column = this.getColumn(this.sortData[s].field); + if (!column) return; + if (column.render && ['date', 'age'].indexOf(column.render.split(':')[0]) != -1) { + this.sortData[s]['field_'] = column.field + '_'; + } + if (column.render && ['time'].indexOf(column.render.split(':')[0]) != -1) { + this.sortData[s]['field_'] = column.field + '_'; + } + } + // process sort + this.records.sort(function (a, b) { + var ret = 0; + for (var s in obj.sortData) { + var fld = obj.sortData[s].field; + if (obj.sortData[s].field_) fld = obj.sortData[s].field_; + var aa = a[fld]; + var bb = b[fld]; + if (String(fld).indexOf('.') != -1) { + aa = obj.parseField(a, fld); + bb = obj.parseField(b, fld); + } + if (typeof aa == 'string') aa = $.trim(aa.toLowerCase()); + if (typeof bb == 'string') bb = $.trim(bb.toLowerCase()); + if (aa > bb) ret = (obj.sortData[s].direction == 'asc' ? 1 : -1); + if (aa < bb) ret = (obj.sortData[s].direction == 'asc' ? -1 : 1); + if (typeof aa != 'object' && typeof bb == 'object') ret = -1; + if (typeof bb != 'object' && typeof aa == 'object') ret = 1; + if (aa == null && bb != null) ret = 1; // all nuls and undefined on bottom + if (aa != null && bb == null) ret = -1; + if (ret != 0) break; + } + return ret; + }); + time = (new Date()).getTime() - time; + if (silent !== true) setTimeout(function () { obj.status('Sorting took ' + time/1000 + ' sec'); }, 10); + return time; + }, + + localSearch: function (silent) { + var url = (typeof this.url != 'object' ? this.url : this.url.get); + if (url) { + console.log('ERROR: grid.localSearch can only be used on local data source, grid.url should be empty.'); + return; + } + var time = (new Date()).getTime(); + var obj = this; + this.total = this.records.length; + // mark all records as shown + this.last.searchIds = []; + // prepare date/time fields + this.prepareData(); + // hide records that did not match + if (this.searchData.length > 0 && !url) { + this.total = 0; + for (var r in this.records) { + var rec = this.records[r]; + var fl = 0; + for (var s in this.searchData) { + var sdata = this.searchData[s]; + var search = this.getSearch(sdata.field); + if (sdata == null) continue; + if (search == null) search = { field: sdata.field, type: sdata.type }; + var val1 = String(obj.parseField(rec, search.field)).toLowerCase(); + if (typeof sdata.value != 'undefined') { + if (!$.isArray(sdata.value)) { + var val2 = String(sdata.value).toLowerCase(); + } else { + var val2 = sdata.value[0]; + var val3 = sdata.value[1]; + } + } + switch (sdata.operator) { + case 'is': + if (rec[search.field] == sdata.value) fl++; // do not hide record + if (search.type == 'date') { + var val1 = w2utils.formatDate(rec[search.field + '_'], 'yyyy-mm-dd'); + var val2 = w2utils.formatDate(val2, 'yyyy-mm-dd'); + if (val1 == val2) fl++; + } + if (search.type == 'time') { + var val1 = w2utils.formatTime(rec[search.field + '_'], 'h24:mi'); + var val2 = w2utils.formatTime(val2, 'h24:mi'); + if (val1 == val2) fl++; + } + break; + case 'between': + if (['int', 'float', 'money', 'currency', 'percent'].indexOf(search.type) != -1) { + if (parseFloat(rec[search.field]) >= parseFloat(val2) && parseFloat(rec[search.field]) <= parseFloat(val3)) fl++; + } + if (search.type == 'date') { + var val1 = rec[search.field + '_']; + var val2 = w2utils.isDate(val2, w2utils.settings.date_format, true); + var val3 = w2utils.isDate(val3, w2utils.settings.date_format, true); + if (val3 != null) val3 = new Date(val3.getTime() + 86400000); // 1 day + if (val1 >= val2 && val1 < val3) fl++; + } + if (search.type == 'time') { + var val1 = rec[search.field + '_']; + var val2 = w2utils.isTime(val2, true); + var val3 = w2utils.isTime(val3, true); + val2 = (new Date()).setHours(val2.hours, val2.minutes, val2.seconds ? val2.seconds : 0, 0); + val3 = (new Date()).setHours(val3.hours, val3.minutes, val3.seconds ? val3.seconds : 0, 0); + if (val1 >= val2 && val1 < val3) fl++; + } + break; + case 'in': + var tmp = sdata.value; + if (sdata.svalue) tmp = sdata.svalue; + if (tmp.indexOf(val1) !== -1) fl++; + break; + case 'not in': + var tmp = sdata.value; + if (sdata.svalue) tmp = sdata.svalue; + if (tmp.indexOf(val1) == -1) fl++; + break; + case 'begins': + case 'begins with': // need for back compatib. + if (val1.indexOf(val2) == 0) fl++; // do not hide record + break; + case 'contains': + if (val1.indexOf(val2) >= 0) fl++; // do not hide record + break; + case 'ends': + case 'ends with': // need for back compatib. + if (val1.indexOf(val2) == val1.length - val2.length) fl++; // do not hide record + break; + } + } + if ((this.last.logic == 'OR' && fl != 0) || (this.last.logic == 'AND' && fl == this.searchData.length)) this.last.searchIds.push(parseInt(r)); + } + this.total = this.last.searchIds.length; + } + time = (new Date()).getTime() - time; + if (silent !== true) setTimeout(function () { obj.status('Search took ' + time/1000 + ' sec'); }, 10); + return time; + }, + + getRangeData: function (range, extra) { + var rec1 = this.get(range[0].recid, true); + var rec2 = this.get(range[1].recid, true); + var col1 = range[0].column; + var col2 = range[1].column; + + var res = []; + if (col1 == col2) { // one row + for (var r = rec1; r <= rec2; r++) { + var record = this.records[r]; + var dt = record[this.columns[col1].field] || null; + if (extra !== true) { + res.push(dt); + } else { + res.push({ data: dt, column: col1, index: r, record: record }); + } + } + } else if (rec1 == rec2) { // one line + var record = this.records[rec1]; + for (var i = col1; i <= col2; i++) { + var dt = record[this.columns[i].field] || null; + if (extra !== true) { + res.push(dt); + } else { + res.push({ data: dt, column: i, index: rec1, record: record }); + } + } + } else { + for (var r = rec1; r <= rec2; r++) { + var record = this.records[r]; + res.push([]); + for (var i = col1; i <= col2; i++) { + var dt = record[this.columns[i].field]; + if (extra !== true) { + res[res.length-1].push(dt); + } else { + res[res.length-1].push({ data: dt, column: i, index: r, record: record }); + } + } + } + } + return res; + }, + + addRange: function (ranges) { + var added = 0; + if (this.selectType == 'row') return added; + if (!$.isArray(ranges)) ranges = [ranges]; + // if it is selection + for (var r in ranges) { + if (typeof ranges[r] != 'object') ranges[r] = { name: 'selection' }; + if (ranges[r].name == 'selection') { + if (this.show.selectionBorder === false) continue; + var sel = this.getSelection(); + if (sel.length == 0) { + this.removeRange(ranges[r].name); + continue; + } else { + var first = sel[0]; + var last = sel[sel.length-1]; + var td1 = $('#grid_'+ this.name +'_rec_'+ first.recid + ' td[col='+ first.column +']'); + var td2 = $('#grid_'+ this.name +'_rec_'+ last.recid + ' td[col='+ last.column +']'); + } + } else { // other range + var first = ranges[r].range[0]; + var last = ranges[r].range[1]; + var td1 = $('#grid_'+ this.name +'_rec_'+ first.recid + ' td[col='+ first.column +']'); + var td2 = $('#grid_'+ this.name +'_rec_'+ last.recid + ' td[col='+ last.column +']'); + } + if (first) { + var rg = { + name: ranges[r].name, + range: [{ recid: first.recid, column: first.column }, { recid: last.recid, column: last.column }], + style: ranges[r].style || '' + }; + // add range + var ind = false; + for (var t in this.ranges) if (this.ranges[t].name == ranges[r].name) { ind = r; break; } + if (ind !== false) { + this.ranges[ind] = rg; + } else { + this.ranges.push(rg); + } + added++ + } + } + this.refreshRanges(); + return added; + }, + + removeRange: function () { + var removed = 0; + for (var a = 0; a < arguments.length; a++) { + var name = arguments[a]; + $('#grid_'+ this.name +'_'+ name).remove(); + for (var r = this.ranges.length-1; r >= 0; r--) { + if (this.ranges[r].name == name) { + this.ranges.splice(r, 1); + removed++; + } + } + } + return removed; + }, + + refreshRanges: function () { + var obj = this; + var time = (new Date()).getTime(); + var rec = $('#grid_'+ this.name +'_records'); + for (var r in this.ranges) { + var rg = this.ranges[r]; + var first = rg.range[0]; + var last = rg.range[1]; + var td1 = $('#grid_'+ this.name +'_rec_'+ first.recid + ' td[col='+ first.column +']'); + var td2 = $('#grid_'+ this.name +'_rec_'+ last.recid + ' td[col='+ last.column +']'); + if ($('#grid_'+ this.name +'_'+ rg.name).length == 0) { + rec.append('<div id="grid_'+ this.name +'_' + rg.name +'" class="w2ui-selection" style="'+ rg.style +'">'+ + (rg.name == 'selection' ? '<div id="grid_'+ this.name +'_resizer" class="w2ui-selection-resizer"></div>' : '')+ + '</div>'); + } else { + $('#grid_'+ this.name +'_'+ rg.name).attr('style', rg.style); + } + if (td1.length > 0 && td2.length > 0) { + $('#grid_'+ this.name +'_'+ rg.name).css({ + left : (td1.position().left - 1 + rec.scrollLeft()) + 'px', + top : (td1.position().top - 1 + rec.scrollTop()) + 'px', + width : (td2.position().left - td1.position().left + td2.width() + 3) + 'px', + height : (td2.position().top - td1.position().top + td2.height() + 3) + 'px' + }); + } + } + + // add resizer events + $(this.box).find('#grid_'+ this.name +'_resizer').off('mousedown').on('mousedown', mouseStart); + //$(this.box).find('#grid_'+ this.name +'_resizer').off('selectstart').on('selectstart', function () { return false; }); // fixes chrome cursror bug + + var eventData = { phase: 'before', type: 'selectionExtend', target: obj.name, originalRange: null, newRange: null }; + + function mouseStart (event) { + var sel = obj.getSelection(); + obj.last.move = { + type : 'expand', + x : event.screenX, + y : event.screenY, + divX : 0, + divY : 0, + recid : sel[0].recid, + column : sel[0].column, + originalRange : [{ recid: sel[0].recid, column: sel[0].column }, { recid: sel[sel.length-1].recid, column: sel[sel.length-1].column }], + newRange : [{ recid: sel[0].recid, column: sel[0].column }, { recid: sel[sel.length-1].recid, column: sel[sel.length-1].column }] + }; + $(document).off('mousemove', mouseMove).on('mousemove', mouseMove); + $(document).off('mouseup', mouseStop).on('mouseup', mouseStop); + } + + function mouseMove (event) { + var mv = obj.last.move; + if (!mv || mv.type != 'expand') return; + mv.divX = (event.screenX - mv.x); + mv.divY = (event.screenY - mv.y); + // find new cell + var recid, column; + var tmp = event.originalEvent.target; + if (tmp.tagName != 'TD') tmp = $(tmp).parents('td')[0]; + if (typeof $(tmp).attr('col') != 'undefined') column = parseInt($(tmp).attr('col')); + tmp = $(tmp).parents('tr')[0]; + recid = $(tmp).attr('recid'); + // new range + if (mv.newRange[1].recid == recid && mv.newRange[1].column == column) return; + var prevNewRange = $.extend({}, mv.newRange); + mv.newRange = [{ recid: mv.recid, column: mv.column }, { recid: recid, column: column }]; + // event before + eventData = obj.trigger($.extend(eventData, { originalRange: mv.originalRange, newRange : mv.newRange })); + if (eventData.isCancelled === true) { + mv.newRange = prevNewRange; + eventData.newRange = prevNewRange; + return; + } else { + // default behavior + obj.removeRange('grid-selection-expand'); + obj.addRange({ + name : 'grid-selection-expand', + range : eventData.newRange, + style : 'background-color: rgba(100,100,100,0.1); border: 2px dotted rgba(100,100,100,0.5);' + }); + } + } + + function mouseStop (event) { + // default behavior + obj.removeRange('grid-selection-expand'); + delete obj.last.move; + $(document).off('mousemove', mouseMove); + $(document).off('mouseup', mouseStop); + // event after + obj.trigger($.extend(eventData, { phase: 'after' })); + } + + return (new Date()).getTime() - time; + }, + + select: function () { + var selected = 0; + var sel = this.last.selection; + if (!this.multiSelect) this.selectNone(); + for (var a = 0; a < arguments.length; a++) { + var recid = typeof arguments[a] == 'object' ? arguments[a].recid : arguments[a]; + var record = this.get(recid); + if (record == null) continue; + var index = this.get(recid, true); + var recEl = $('#grid_'+ this.name +'_rec_'+ w2utils.escapeId(recid)); + if (this.selectType == 'row') { + if (sel.indexes.indexOf(index) >= 0) continue; + // event before + var eventData = this.trigger({ phase: 'before', type: 'select', target: this.name, recid: recid, index: index }); + if (eventData.isCancelled === true) continue; + // default action + sel.indexes.push(index); + sel.indexes.sort(function(a, b) { return a-b }); + recEl.addClass('w2ui-selected').data('selected', 'yes'); + recEl.find('.w2ui-grid-select-check').prop("checked", true); + selected++; + } else { + var col = arguments[a].column; + if (!w2utils.isInt(col)) { // select all columns + var cols = []; + for (var c in this.columns) { if (this.columns[c].hidden) continue; cols.push({ recid: recid, column: parseInt(c) }); } + if (!this.multiSelect) cols = cols.splice(0, 1); + return this.select.apply(this, cols); + } + var s = sel.columns[index] || []; + if ($.isArray(s) && s.indexOf(col) != -1) continue; + // event before + var eventData = this.trigger({ phase: 'before', type: 'select', target: this.name, recid: recid, index: index, column: col }); + if (eventData.isCancelled === true) continue; + // default action + if (sel.indexes.indexOf(index) == -1) { + sel.indexes.push(index); + sel.indexes.sort(function(a, b) { return a-b }); + } + s.push(col); + s.sort(function(a, b) { return a-b }); // sort function must be for numerical sort + recEl.find(' > td[col='+ col +']').addClass('w2ui-selected'); + selected++; + recEl.data('selected', 'yes'); + recEl.find('.w2ui-grid-select-check').prop("checked", true); + // save back to selection object + sel.columns[index] = s; + } + // event after + this.trigger($.extend(eventData, { phase: 'after' })); + } + // all selected? + if (sel.indexes.length == this.records.length || (this.searchData.length !== 0 && sel.indexes.length == this.last.searchIds.length)) { + $('#grid_'+ this.name +'_check_all').prop('checked', true); + } else { + $('#grid_'+ this.name +'_check_all').prop('checked', false); + } + this.status(); + this.addRange('selection'); + return selected; + }, + + unselect: function () { + var unselected = 0; + var sel = this.last.selection; + for (var a = 0; a < arguments.length; a++) { + var recid = typeof arguments[a] == 'object' ? arguments[a].recid : arguments[a]; + var record = this.get(recid); + if (record == null) continue; + var index = this.get(record.recid, true); + var recEl = $('#grid_'+ this.name +'_rec_'+ w2utils.escapeId(recid)); + if (this.selectType == 'row') { + if (sel.indexes.indexOf(index) == -1) continue; + // event before + var eventData = this.trigger({ phase: 'before', type: 'unselect', target: this.name, recid: recid, index: index }); + if (eventData.isCancelled === true) continue; + // default action + sel.indexes.splice(sel.indexes.indexOf(index), 1); + recEl.removeClass('w2ui-selected').removeData('selected'); + if (recEl.length != 0) recEl[0].style.cssText = 'height: '+ this.recordHeight +'px; ' + recEl.attr('custom_style'); + recEl.find('.w2ui-grid-select-check').prop("checked", false); + unselected++; + } else { + var col = arguments[a].column; + if (!w2utils.isInt(col)) { // unselect all columns + var cols = []; + for (var c in this.columns) { if (this.columns[c].hidden) continue; cols.push({ recid: recid, column: parseInt(c) }); } + return this.unselect.apply(this, cols); + } + var s = sel.columns[index]; + if (!$.isArray(s) || s.indexOf(col) == -1) continue; + // event before + var eventData = this.trigger({ phase: 'before', type: 'unselect', target: this.name, recid: recid, column: col }); + if (eventData.isCancelled === true) continue; + // default action + s.splice(s.indexOf(col), 1); + $('#grid_'+ this.name +'_rec_'+ w2utils.escapeId(recid) + ' > td[col='+ col +']').removeClass('w2ui-selected'); + unselected++; + if (s.length == 0) { + delete sel.columns[index]; + sel.indexes.splice(sel.indexes.indexOf(index), 1); + recEl.removeData('selected'); + recEl.find('.w2ui-grid-select-check').prop("checked", false); + } + } + // event after + this.trigger($.extend(eventData, { phase: 'after' })); + } + // all selected? + if (sel.indexes.length == this.records.length || (this.searchData.length !== 0 && sel.indexes.length == this.last.searchIds.length)) { + $('#grid_'+ this.name +'_check_all').prop('checked', true); + } else { + $('#grid_'+ this.name +'_check_all').prop('checked', false); + } + // show number of selected + this.status(); + this.addRange('selection'); + return unselected; + }, + + selectAll: function () { + if (this.multiSelect === false) return; + // event before + var eventData = this.trigger({ phase: 'before', type: 'select', target: this.name, all: true }); + if (eventData.isCancelled === true) return; + // default action + var url = (typeof this.url != 'object' ? this.url : this.url.get); + var sel = this.last.selection; + var cols = []; + for (var c in this.columns) cols.push(parseInt(c)); + // if local data source and searched + sel.indexes = []; + if (!url && this.searchData.length !== 0) { + // local search applied + for (var i = 0; i < this.last.searchIds.length; i++) { + sel.indexes.push(this.last.searchIds[i]); + if (this.selectType != 'row') sel.columns[this.last.searchIds[i]] = cols.slice(); // .slice makes copy of the array + } + } else { + var buffered = this.records.length; + if (this.searchData.length != 0 && !this.url) buffered = this.last.searchIds.length; + for (var i = 0; i < buffered; i++) { + sel.indexes.push(i); + if (this.selectType != 'row') sel.columns[i] = cols.slice(); // .slice makes copy of the array + } + } + this.refresh(); + // enable/disable toolbar buttons + var sel = this.getSelection(); + if (sel.length == 1) this.toolbar.enable('w2ui-edit'); else this.toolbar.disable('w2ui-edit'); + if (sel.length >= 1) this.toolbar.enable('w2ui-delete'); else this.toolbar.disable('w2ui-delete'); + this.addRange('selection'); + // event after + this.trigger($.extend(eventData, { phase: 'after' })); + }, + + selectNone: function () { + // event before + var eventData = this.trigger({ phase: 'before', type: 'unselect', target: this.name, all: true }); + if (eventData.isCancelled === true) return; + // default action + var sel = this.last.selection; + for (var s in sel.indexes) { + var index = sel.indexes[s]; + var rec = this.records[index]; + var recid = rec ? rec.recid : null; + var recEl = $('#grid_'+ this.name +'_rec_'+ w2utils.escapeId(recid)); + recEl.removeClass('w2ui-selected').removeData('selected'); + recEl.find('.w2ui-grid-select-check').prop("checked", false); + // for not rows + if (this.selectType != 'row') { + var cols = sel.columns[index]; + for (var c in cols) recEl.find(' > td[col='+ cols[c] +']').removeClass('w2ui-selected'); + } + } + sel.indexes = []; + sel.columns = {}; + this.toolbar.disable('w2ui-edit', 'w2ui-delete'); + this.removeRange('selection'); + $('#grid_'+ this.name +'_check_all').prop('checked', false); + // event after + this.trigger($.extend(eventData, { phase: 'after' })); + }, + + getSelection: function (returnIndex) { + var ret = []; + var sel = this.last.selection; + if (this.selectType == 'row') { + for (var s in sel.indexes) { + if (!this.records[sel.indexes[s]]) continue; + if (returnIndex === true) ret.push(sel.indexes[s]); else ret.push(this.records[sel.indexes[s]].recid); + } + return ret; + } else { + for (var s in sel.indexes) { + var cols = sel.columns[sel.indexes[s]]; + if (!this.records[sel.indexes[s]]) continue; + for (var c in cols) { + ret.push({ recid: this.records[sel.indexes[s]].recid, index: parseInt(sel.indexes[s]), column: cols[c] }); + } + } + return ret; + } + }, + + search: function (field, value) { + var obj = this; + var url = (typeof this.url != 'object' ? this.url : this.url.get); + var searchData = []; + var last_multi = this.last.multi; + var last_logic = this.last.logic; + var last_field = this.last.field; + var last_search = this.last.search; + // 1: search() - advanced search (reads from popup) + if (arguments.length == 0) { + last_search = ''; + // advanced search + for (var s in this.searches) { + var search = this.searches[s]; + var operator = $('#grid_'+ this.name + '_operator_'+s).val(); + var field1 = $('#grid_'+ this.name + '_field_'+s); + var field2 = $('#grid_'+ this.name + '_field2_'+s); + var value1 = field1.val(); + var value2 = field2.val(); + var svalue = null; + if (['int', 'float', 'money', 'currency', 'percent'].indexOf(search.type) != -1) { + var fld1 = field1.data('w2field'); + var fld2 = field2.data('w2field'); + if (fld1) value1 = fld1.clean(value1); + if (fld2) value2 = fld2.clean(value2); + } + if (['list', 'enum'].indexOf(search.type) != -1) { + value1 = field1.data('selected') || {}; + if ($.isArray(value1)) { + svalue = []; + for (var v in value1) { + svalue.push(w2utils.isFloat(value1[v].id) ? parseFloat(value1[v].id) : String(value1[v].id).toLowerCase()); + delete value1[v].hidden; + } + } else { + value1 = value1.id || ''; + } + } + if ((value1 != '' && value1 != null) || (typeof value2 != 'undefined' && value2 != '')) { + var tmp = { + field : search.field, + type : search.type, + operator : operator + } + if (operator == 'between') { + $.extend(tmp, { value: [value1, value2] }); + } else if (operator == 'in' && typeof value1 == 'string') { + $.extend(tmp, { value: value1.split(',') }); + } else if (operator == 'not in' && typeof value1 == 'string') { + $.extend(tmp, { value: value1.split(',') }); + } else { + $.extend(tmp, { value: value1 }); + } + if (svalue) $.extend(tmp, { svalue: svalue }); + // conver date to unix time + try { + if (search.type == 'date' && operator == 'between') { + tmp.value[0] = value1; // w2utils.isDate(value1, w2utils.settings.date_format, true).getTime(); + tmp.value[1] = value2; // w2utils.isDate(value2, w2utils.settings.date_format, true).getTime(); + } + if (search.type == 'date' && operator == 'is') { + tmp.value = value1; // w2utils.isDate(value1, w2utils.settings.date_format, true).getTime(); + } + } catch (e) { + + } + searchData.push(tmp); + } + } + if (searchData.length > 0 && !url) { + last_multi = true; + last_logic = 'AND'; + } else { + last_multi = true; + last_logic = 'AND'; + } + } + // 2: search(field, value) - regular search + if (typeof field == 'string') { + last_field = field; + last_search = value; + last_multi = false; + last_logic = 'OR'; + // loop through all searches and see if it applies + if (typeof value != 'undefined') { + if (field.toLowerCase() == 'all') { + // if there are search fields loop thru them + if (this.searches.length > 0) { + for (var s in this.searches) { + var search = this.searches[s]; + if (search.type == 'text' || (search.type == 'alphanumeric' && w2utils.isAlphaNumeric(value)) + || (search.type == 'int' && w2utils.isInt(value)) || (search.type == 'float' && w2utils.isFloat(value)) + || (search.type == 'percent' && w2utils.isFloat(value)) || (search.type == 'hex' && w2utils.isHex(value)) + || (search.type == 'currency' && w2utils.isMoney(value)) || (search.type == 'money' && w2utils.isMoney(value)) + || (search.type == 'date' && w2utils.isDate(value)) ) { + var tmp = { + field : search.field, + type : search.type, + operator : (search.type == 'text' ? 'contains' : 'is'), + value : value + }; + searchData.push(tmp); + } + // range in global search box + if (['int', 'float', 'money', 'currency', 'percent'].indexOf(search.type) != -1 && String(value).indexOf('-') != -1) { + var t = String(value).split('-'); + var tmp = { + field : search.field, + type : search.type, + operator : 'between', + value : [t[0], t[1]] + }; + searchData.push(tmp); + } + } + } else { + // no search fields, loop thru columns + for (var c in this.columns) { + var tmp = { + field : this.columns[c].field, + type : 'text', + operator : 'contains', + value : value + }; + searchData.push(tmp); + } + } + } else { + var el = $('#grid_'+ this.name +'_search_all'); + var search = this.getSearch(field); + if (search == null) search = { field: field, type: 'text' }; + if (search.field == field) this.last.caption = search.caption; + if (search.type == 'list') { + var tmp = el.data('selected'); + if (tmp && !$.isEmptyObject(tmp)) value = tmp.id; + } + if (value != '') { + var op = 'contains'; + var val = value; + if (['date', 'time', 'list'].indexOf(search.type) != -1) op = 'is'; + if (search.type == 'int' && value != '') { + op = 'is'; + if (String(value).indexOf('-') != -1) { + var tmp = value.split('-'); + if (tmp.length == 2) { + op = 'between'; + val = [parseInt(tmp[0]), parseInt(tmp[1])]; + } + } + if (String(value).indexOf(',') != -1) { + var tmp = value.split(','); + op = 'in'; + val = []; + for (var t in tmp) val.push(tmp[t]); + } + } + var tmp = { + field : search.field, + type : search.type, + operator : op, + value : val + } + searchData.push(tmp); + } + } + } + } + // 3: search([ { field, value, [operator,] [type] }, { field, value, [operator,] [type] } ], logic) - submit whole structure + if ($.isArray(field)) { + var logic = 'AND'; + if (typeof value == 'string') { + logic = value.toUpperCase(); + if (logic != 'OR' && logic != 'AND') logic = 'AND'; + } + last_search = ''; + last_multi = true; + last_logic = logic; + for (var f in field) { + var data = field[f]; + var search = this.getSearch(data.field); + if (search == null) search = { type: 'text', operator: 'contains' }; + // merge current field and search if any + searchData.push($.extend(true, {}, search, data)); + } + } + // event before + var eventData = this.trigger({ phase: 'before', type: 'search', target: this.name, searchData: searchData, + searchField: (field ? field : 'multi'), searchValue: (value ? value : 'multi') }); + if (eventData.isCancelled === true) return; + // default action + this.searchData = eventData.searchData; + this.last.field = last_field; + this.last.search = last_search; + this.last.multi = last_multi; + this.last.logic = last_logic; + this.last.scrollTop = 0; + this.last.scrollLeft = 0; + this.last.selection.indexes = []; + this.last.selection.columns = {}; + // -- clear all search field + this.searchClose(); + this.set({ expanded: false }, true); + // apply search + if (url) { + this.last.xhr_offset = 0; + this.reload(); + } else { + // local search + this.localSearch(); + this.refresh(); + } + // event after + this.trigger($.extend(eventData, { phase: 'after' })); + }, + + searchOpen: function () { + if (!this.box) return; + if (this.searches.length == 0) return; + var obj = this; + // show search + $('#tb_'+ this.name +'_toolbar_item_w2ui-search-advanced').w2overlay( + this.getSearchesHTML(), { + name : 'searches-'+ this.name, + left : -10, + 'class' : 'w2ui-grid-searches', + onShow : function () { + if (obj.last.logic == 'OR') obj.searchData = []; + obj.initSearches(); + $('#w2ui-overlay-searches-'+ this.name +' .w2ui-grid-searches').data('grid-name', obj.name); + var sfields = $('#w2ui-overlay-searches-'+ this.name +' .w2ui-grid-searches *[rel=search]'); + if (sfields.length > 0) sfields[0].focus(); + } + } + ); + }, + + searchClose: function () { + if (!this.box) return; + if (this.searches.length == 0) return; + if (this.toolbar) this.toolbar.uncheck('w2ui-search-advanced') + // hide search + if ($('#w2ui-overlay-searches-'+ this.name +' .w2ui-grid-searches').length > 0) { + $().w2overlay('', { name: 'searches-'+ this.name }); + } + }, + + searchShowFields: function () { + var el = $('#grid_'+ this.name +'_search_all'); + var html = '<div class="w2ui-select-field"><table>'; + for (var s = -1; s < this.searches.length; s++) { + var search = this.searches[s]; + if (s == -1) { + if (!this.multiSearch) continue; + search = { field: 'all', caption: w2utils.lang('All Fields') }; + } else { + if (this.searches[s].hidden === true) continue; + } + html += '<tr '+ (w2utils.isIOS ? 'onTouchStart' : 'onClick') +'="w2ui[\''+ this.name +'\'].initAllField(\''+ search.field +'\')">'+ + ' <td><input type="radio" tabIndex="-1" '+ (search.field == this.last.field ? 'checked' : '') +'></td>'+ + ' <td>'+ search.caption +'</td>'+ + '</tr>'; + } + html += "</table></div>"; + // need timer otherwise does nto show with list type + setTimeout(function () { + $(el).w2overlay(html, { left: -10 }); + }, 1); + }, + + initAllField: function (field, value) { + var el = $('#grid_'+ this.name +'_search_all'); + var search = this.getSearch(field); + if (field == 'all') { + search = { field: 'all', caption: w2utils.lang('All Fields') }; + el.w2field('clear'); + el.change().focus(); + } else { + var st = search.type; + if (['enum', 'select'].indexOf(st) != -1) st = 'list'; + el.w2field(st, $.extend({}, search.options, { suffix: '', autoFormat: false, selected: value })); + if (['list', 'enum'].indexOf(search.type) != -1) { + this.last.search = ''; + this.last.item = ''; + el.val(''); + } + // set focus + setTimeout(function () { + el.focus(); /* do not do el.change() as it will refresh grid and pull from server */ + }, 1); + } + // update field + if (this.last.search != '') { + this.search(search.field, this.last.search); + } else { + this.last.field = search.field; + this.last.caption = search.caption; + } + el.attr('placeholder', search.caption); + $().w2overlay(); + }, + + searchReset: function (noRefresh) { + // event before + var eventData = this.trigger({ phase: 'before', type: 'search', target: this.name, searchData: [] }); + if (eventData.isCancelled === true) return; + // default action + this.searchData = []; + this.last.search = ''; + this.last.logic = 'OR'; + // --- do not reset to All Fields (I think) + // if (this.last.multi) { + // if (!this.multiSearch) { + // this.last.field = this.searches[0].field; + // this.last.caption = this.searches[0].caption; + // } else { + // this.last.field = 'all'; + // this.last.caption = w2utils.lang('All Fields'); + // } + // } + this.last.multi = false; + this.last.xhr_offset = 0; + // reset scrolling position + this.last.scrollTop = 0; + this.last.scrollLeft = 0; + this.last.selection.indexes = []; + this.last.selection.columns = {}; + // -- clear all search field + this.searchClose(); + $('#grid_'+ this.name +'_search_all').val(''); + // apply search + if (!noRefresh) this.reload(); + // event after + this.trigger($.extend(eventData, { phase: 'after' })); + }, + + clear: function (noRefresh) { + // this.offset = 0; // clear should not reset offset + // this.total = 0; // clear should not reset total + this.records = []; + this.summary = []; + this.last.scrollTop = 0; + this.last.scrollLeft = 0; + this.last.range_start = null; + this.last.range_end = null; + // this.last.xhr_offset = 0; // clear should not reset offset + if (!noRefresh) this.refresh(); + }, + + reset: function (noRefresh) { + // reset last remembered state + this.offset = 0; + this.total = 0; + this.last.scrollTop = 0; + this.last.scrollLeft = 0; + this.last.selection.indexes = []; + this.last.selection.columns = {}; + this.last.range_start = null; + this.last.range_end = null; + this.last.xhr_offset = 0; + this.searchReset(noRefresh); + // initial sort + if (this.last.sortData != null ) this.sortData = this.last.sortData; + // select none without refresh + this.set({ expanded: false }, true); + // refresh + if (!noRefresh) this.refresh(); + }, + + skip: function (offset) { + var url = (typeof this.url != 'object' ? this.url : this.url.get); + if (url) { + this.offset = parseInt(offset); + if (this.offset > this.total) this.offset = this.total - this.limit; + if (this.offset < 0 || !w2utils.isInt(this.offset)) this.offset = 0; + this.records = []; + this.last.xhr_offset = 0; + this.last.pull_more = true; + this.last.scrollTop = 0; + this.last.scrollLeft = 0; + $('#grid_'+ this.name +'_records').prop('scrollTop', 0); + this.reload(); + } else { + console.log('ERROR: grid.skip() can only be called when you have remote data source.'); + } + }, + + load: function (url, callBack) { + if (typeof url == 'undefined') { + console.log('ERROR: You need to provide url argument when calling .load() method of "'+ this.name +'" object.'); + return; + } + // default action + this.request('get-records', {}, url, callBack); + }, + + reload: function (callBack) { + var url = (typeof this.url != 'object' ? this.url : this.url.get); + if (url) { + this.clear(true); + this.request('get-records', {}, null, callBack); + } else { + this.last.scrollTop = 0; + this.last.scrollLeft = 0; + this.last.range_start = null; + this.last.range_end = null; + this.localSearch(); + this.refresh(); + if (typeof callBack == 'function') callBack({ status: 'success' }); + } + }, + + request: function (cmd, add_params, url, callBack) { + if (typeof add_params == 'undefined') add_params = {}; + if (typeof url == 'undefined' || url == '' || url == null) url = this.url; + if (url == '' || url == null) return; + // build parameters list + var params = {}; + if (!w2utils.isInt(this.offset)) this.offset = 0; + if (!w2utils.isInt(this.last.xhr_offset)) this.last.xhr_offset = 0; + // add list params + params['cmd'] = cmd; + params['selected'] = this.getSelection(); + params['limit'] = this.limit; + params['offset'] = parseInt(this.offset) + this.last.xhr_offset; + params['search'] = this.searchData; + params['searchLogic'] = this.last.logic; + params['sort'] = this.sortData; + if (this.searchData.length == 0) { + delete params['search']; + delete params['searchLogic']; + } + if (this.sortData.length == 0) { + delete params['sort']; + } + // append other params + $.extend(params, this.postData); + $.extend(params, add_params); + // event before + if (cmd == 'get-records') { + var eventData = this.trigger({ phase: 'before', type: 'request', target: this.name, url: url, postData: params }); + if (eventData.isCancelled === true) { if (typeof callBack == 'function') callBack({ status: 'error', message: 'Request aborted.' }); return; } + } else { + var eventData = { url: url, postData: params }; + } + // call server to get data + var obj = this; + if (this.last.xhr_offset == 0) { + this.lock(this.msgRefresh, true); + } else { + var more = $('#grid_'+ this.name +'_rec_more'); + if (this.autoLoad === true) { + more.show().find('td').html('<div><div style="width: 20px; height: 20px;" class="w2ui-spinner"></div></div>'); + } else { + more.find('td').html('<div>'+ w2utils.lang('Load') + ' ' + obj.limit + ' ' + w2utils.lang('More') + '...</div>'); + } + } + if (this.last.xhr) try { this.last.xhr.abort(); } catch (e) {}; + // URL + var url = (typeof eventData.url != 'object' ? eventData.url : eventData.url.get); + if (params.cmd == 'save-records' && typeof eventData.url == 'object') url = eventData.url.save; + if (params.cmd == 'delete-records' && typeof eventData.url == 'object') url = eventData.url.remove; + // process url with routeData + if (!$.isEmptyObject(obj.routeData)) { + var info = w2utils.parseRoute(url); + if (info.keys.length > 0) { + for (var k = 0; k < info.keys.length; k++) { + if (obj.routeData[info.keys[k].name] == null) continue; + url = url.replace((new RegExp(':'+ info.keys[k].name, 'g')), obj.routeData[info.keys[k].name]); + } + } + } + // ajax ptions + var ajaxOptions = { + type : 'POST', + url : url, + data : eventData.postData, + dataType : 'text' // expected data type from server + }; + if (w2utils.settings.dataType == 'HTTP') { + ajaxOptions.data = (typeof ajaxOptions.data == 'object' ? String($.param(ajaxOptions.data, false)).replace(/%5B/g, '[').replace(/%5D/g, ']') : ajaxOptions.data); + } + if (w2utils.settings.dataType == 'RESTFULL') { + ajaxOptions.type = 'GET'; + if (params.cmd == 'save-records') ajaxOptions.type = 'PUT'; // so far it is always update + if (params.cmd == 'delete-records') ajaxOptions.type = 'DELETE'; + ajaxOptions.data = (typeof ajaxOptions.data == 'object' ? String($.param(ajaxOptions.data, false)).replace(/%5B/g, '[').replace(/%5D/g, ']') : ajaxOptions.data); + } + if (w2utils.settings.dataType == 'JSON') { + ajaxOptions.type = 'POST'; + ajaxOptions.data = JSON.stringify(ajaxOptions.data); + ajaxOptions.contentType = 'application/json'; + } + if (this.method) ajaxOptions.type = this.method; + + this.last.xhr_cmd = params.cmd; + this.last.xhr_start = (new Date()).getTime(); + this.last.xhr = $.ajax(ajaxOptions) + .done(function (data, status, xhr) { + obj.requestComplete(status, cmd, callBack); + }) + .fail(function (xhr, status, error) { + // trigger event + var errorObj = { status: status, error: error, rawResponseText: xhr.responseText }; + var eventData2 = obj.trigger({ phase: 'before', type: 'error', error: errorObj, xhr: xhr }); + if (eventData2.isCancelled === true) return; + // default behavior + if (status != 'abort') { + var data; + try { data = $.parseJSON(xhr.responseText) } catch (e) {} + console.log('ERROR: Server communication failed.', + '\n EXPECTED:', { status: 'success', total: 5, records: [{ recid: 1, field: 'value' }] }, + '\n OR:', { status: 'error', message: 'error message' }, + '\n RECEIVED:', typeof data == 'object' ? data : xhr.responseText); + } + obj.requestComplete('error', cmd, callBack); + // event after + obj.trigger($.extend(eventData2, { phase: 'after' })); + }); + if (cmd == 'get-records') { + // event after + this.trigger($.extend(eventData, { phase: 'after' })); + } + }, + + requestComplete: function(status, cmd, callBack) { + var obj = this; + this.unlock(); + setTimeout(function () { obj.status(w2utils.lang('Server Response') + ' ' + ((new Date()).getTime() - obj.last.xhr_start)/1000 +' ' + w2utils.lang('sec')); }, 10); + this.last.pull_more = false; + this.last.pull_refresh = true; + + // event before + var event_name = 'load'; + if (this.last.xhr_cmd == 'save-records') event_name = 'save'; + if (this.last.xhr_cmd == 'delete-records') event_name = 'deleted'; + var eventData = this.trigger({ phase: 'before', target: this.name, type: event_name, xhr: this.last.xhr, status: status }); + if (eventData.isCancelled === true) { + if (typeof callBack == 'function') callBack({ status: 'error', message: 'Request aborted.' }); + return; + } + // parse server response + var data; + var responseText = this.last.xhr.responseText; + if (status != 'error') { + // default action + if (typeof responseText != 'undefined' && responseText != '') { + // check if the onLoad handler has not already parsed the data + if (typeof responseText == "object") { + data = responseText; + } else { + if (typeof obj.parser == 'function') { + data = obj.parser(responseText); + if (typeof data != 'object') { + console.log('ERROR: Your parser did not return proper object'); + } + } else { + // $.parseJSON or $.getJSON did not work because those expect perfect JSON data - where everything is in double quotes + // + // TODO: avoid (potentially malicious) code injection from the response. + try { eval('data = '+ responseText); } catch (e) { } + } + } + // convert recids + if (obj.recid) { + for (var r in data.records) { + data.records[r]['recid'] = data.records[r][obj.recid]; + } + } + if (typeof data == 'undefined') { + data = { + status : 'error', + message : this.msgNotJSON, + responseText : responseText + }; + } + if (data['status'] == 'error') { + obj.error(data['message']); + } else { + if (cmd == 'get-records') { + if (this.last.xhr_offset == 0) { + this.records = []; + this.summary = []; + //data.xhr_status=data.status; + delete data.status; + $.extend(true, this, data); + } else { + var records = data.records; + delete data.records; + //data.xhr_status=data.status; + delete data.status; + $.extend(true, this, data); + for (var r in records) { + this.records.push(records[r]); + } + } + } + if (cmd == 'delete-records') { + // reset() also triggers reload + this.reset(); // unselect old selections + return; + } + } + } + } else { + data = { + status : 'error', + message : this.msgAJAXerror, + responseText : responseText + }; + obj.error(this.msgAJAXerror); + } + // event after + var url = (typeof this.url != 'object' ? this.url : this.url.get); + if (!url) { + this.localSort(); + this.localSearch(); + } + this.total = parseInt(this.total); + this.trigger($.extend(eventData, { phase: 'after' })); + // do not refresh if loading on infinite scroll + if (this.last.xhr_offset == 0) this.refresh(); else this.scroll(); + // call back + if (typeof callBack == 'function') callBack(data); + }, + + error: function (msg) { + var obj = this; + // let the management of the error outside of the grid + var eventData = this.trigger({ target: this.name, type: 'error', message: msg , xhr: this.last.xhr }); + if (eventData.isCancelled === true) { + if (typeof callBack == 'function') callBack({ status: 'error', message: 'Request aborted.' }); + return; + } + w2alert(msg, 'Error'); + // event after + this.trigger($.extend(eventData, { phase: 'after' })); + }, + + getChanges: function () { + var changes = []; + for (var r in this.records) { + var rec = this.records[r]; + if (typeof rec['changes'] != 'undefined') { + changes.push($.extend(true, { recid: rec.recid }, rec.changes)); + } + } + return changes; + }, + + mergeChanges: function () { + var changes = this.getChanges(); + for (var c in changes) { + var record = this.get(changes[c].recid); + for (var s in changes[c]) { + if (s == 'recid') continue; // do not allow to change recid + try { eval('record.' + s + ' = changes[c][s]'); } catch (e) {} + delete record.changes; + } + } + this.refresh(); + }, + + // =================================================== + // -- Action Handlers + + save: function () { + var obj = this; + var changes = this.getChanges(); + // event before + var eventData = this.trigger({ phase: 'before', target: this.name, type: 'submit', changes: changes }); + if (eventData.isCancelled === true) return; + var url = (typeof this.url != 'object' ? this.url : this.url.save); + if (url) { + this.request('save-records', { 'changes' : eventData.changes }, null, + function (data) { + if (data.status !== 'error') { + // only merge changes, if save was successful + obj.mergeChanges(); + } + // event after + obj.trigger($.extend(eventData, { phase: 'after' })); + } + ); + } else { + this.mergeChanges(); + // event after + this.trigger($.extend(eventData, { phase: 'after' })); + } + }, + + editField: function (recid, column, value, event) { + var obj = this; + var index = obj.get(recid, true); + var rec = obj.records[index]; + var col = obj.columns[column]; + var edit = col ? col.editable : null; + if (!rec || !col || !edit || rec.editable === false) return; + if (['enum', 'file'].indexOf(edit.type) != -1) { + console.log('ERROR: input types "enum" and "file" are not supported in inline editing.'); + return; + } + // event before + var eventData = obj.trigger({ phase: 'before', type: 'editField', target: obj.name, recid: recid, column: column, value: value, + index: index, originalEvent: event }); + if (eventData.isCancelled === true) return; + value = eventData.value; + // default behaviour + this.selectNone(); + this.select({ recid: recid, column: column }); + this.last.edit_col = column; + if (['checkbox', 'check'].indexOf(edit.type) != -1) return; + // create input element + var tr = $('#grid_'+ obj.name +'_rec_'+ w2utils.escapeId(recid)); + var el = tr.find('[col='+ column +'] > div'); + if (typeof edit.inTag == 'undefined') edit.inTag = ''; + if (typeof edit.outTag == 'undefined') edit.outTag = ''; + if (typeof edit.style == 'undefined') edit.style = ''; + if (typeof edit.items == 'undefined') edit.items = []; + var val = (rec.changes && typeof rec.changes[col.field] != 'undefined' ? w2utils.stripTags(rec.changes[col.field]) : w2utils.stripTags(rec[col.field])); + if (val == null || typeof val == 'undefined') val = ''; + if (typeof value != 'undefined' && value != null) val = value; + var addStyle = (typeof col.style != 'undefined' ? col.style + ';' : ''); + if (typeof col.render == 'string' && ['number', 'int', 'float', 'money', 'percent'].indexOf(col.render.split(':')[0]) != -1) { + addStyle += 'text-align: right;'; + } + if (edit.type == 'select') { + var html = ''; + for (var i in edit.items) { + html += '<option value="'+ edit.items[i].id +'" '+ (edit.items[i].id == val ? 'selected' : '') +'>'+ edit.items[i].text +'</option>'; + } + el.addClass('w2ui-editable') + .html('<select id="grid_'+ obj.name +'_edit_'+ recid +'_'+ column +'" column="'+ column +'" '+ + ' style="width: 100%; '+ addStyle + edit.style +'" field="'+ col.field +'" recid="'+ recid +'" '+ + ' '+ edit.inTag + + '>'+ html +'</select>' + edit.outTag); + el.find('select').focus() + .on('change', function (event) { + delete obj.last.move; + }) + .on('blur', function (event) { + obj.editChange.call(obj, this, index, column, event); + }); + } else { + el.addClass('w2ui-editable') + .html('<input id="grid_'+ obj.name +'_edit_'+ recid +'_'+ column +'" '+ + ' type="text" style="outline: none; '+ addStyle + edit.style +'" field="'+ col.field +'" recid="'+ recid +'" '+ + ' column="'+ column +'" '+ edit.inTag + + '>' + edit.outTag); + if (value == null) el.find('input').val(val != 'object' ? val : ''); + // init w2field + var input = el.find('input').get(0); + $(input).w2field(edit.type, $.extend(edit, { selected: val })) + // add blur listener + setTimeout(function () { + var tmp = input; + if (edit.type == 'list') { + tmp = $($(input).data('w2field').helpers.focus).find('input'); + if (val != 'object' && val != '') tmp.val(val).css({ opacity: 1 }).prev().css({ opacity: 1 }); + } + $(tmp).on('blur', function (event) { + obj.editChange.call(obj, input, index, column, event); + }); + }, 10); + if (value != null) $(input).val(val != 'object' ? val : ''); + } + setTimeout(function () { + el.find('input, select') + .on('click', function (event) { + event.stopPropagation(); + }) + .on('keydown', function (event) { + var cancel = false; + switch (event.keyCode) { + case 9: // tab + cancel = true; + var next_rec = recid; + var next_col = event.shiftKey ? obj.prevCell(column, true) : obj.nextCell(column, true); + // next or prev row + if (next_col == null) { + var tmp = event.shiftKey ? obj.prevRow(index) : obj.nextRow(index); + if (tmp != null && tmp != index) { + next_rec = obj.records[tmp].recid; + // find first editable row + for (var c in obj.columns) { + var tmp = obj.columns[c].editable; + if (typeof tmp != 'undefined' && ['checkbox', 'check'].indexOf(tmp.type) == -1) { + next_col = parseInt(c); + if (!event.shiftKey) break; + } + } + } + + } + if (next_rec === false) next_rec = recid; + if (next_col == null) next_col = column; + // init new or same record + this.blur(); + setTimeout(function () { + if (obj.selectType != 'row') { + obj.selectNone(); + obj.select({ recid: next_rec, column: next_col }); + } else { + obj.editField(next_rec, next_col, null, event); + } + }, 1); + break; + + case 13: // enter + this.blur(); + var next = event.shiftKey ? obj.prevRow(index) : obj.nextRow(index); + if (next != null && next != index) { + setTimeout(function () { + if (obj.selectType != 'row') { + obj.selectNone(); + obj.select({ recid: obj.records[next].recid, column: column }); + } else { + obj.editField(obj.records[next].recid, column, null, event); + } + }, 100); + } + break; + + case 38: // up arrow + if (!event.shiftKey) break; + cancel = true; + var next = obj.prevRow(index); + if (next != index) { + this.blur(); + setTimeout(function () { + if (obj.selectType != 'row') { + obj.selectNone(); + obj.select({ recid: obj.records[next].recid, column: column }); + } else { + obj.editField(obj.records[next].recid, column, null, event); + } + }, 1); + } + break; + + case 40: // down arrow + if (!event.shiftKey) break; + cancel = true; + var next = obj.nextRow(index); + if (next != null && next != index) { + this.blur(); + setTimeout(function () { + if (obj.selectType != 'row') { + obj.selectNone(); + obj.select({ recid: obj.records[next].recid, column: column }); + } else { + obj.editField(obj.records[next].recid, column, null, event); + } + }, 1); + } + break; + + case 27: // escape + var old = obj.parseField(rec, col.field); + if (rec.changes && typeof rec.changes[col.field] != 'undefined') old = rec.changes[col.field]; + this.value = typeof old != 'undefined' ? old : ''; + this.blur(); + setTimeout(function () { obj.select({ recid: recid, column: column }) }, 1); + break; + } + if (cancel) if (event.preventDefault) event.preventDefault(); + }); + // focus and select + var tmp = el.find('input').focus(); + if (value != null) { + // set cursor to the end + tmp[0].setSelectionRange(tmp.val().length, tmp.val().length); + } else { + tmp.select(); + } + + }, 1); + // event after + obj.trigger($.extend(eventData, { phase: 'after' })); + }, + + editChange: function (el, index, column, event) { + // all other fields + var summary = index < 0; + index = index < 0 ? -index - 1 : index; + var records = summary ? this.summary : this.records; + var rec = records[index]; + var tr = $('#grid_'+ this.name +'_rec_'+ w2utils.escapeId(rec.recid)); + var col = this.columns[column]; + var new_val = el.value; + var old_val = this.parseField(rec, col.field); + var tmp = $(el).data('w2field'); + if (tmp) { + new_val = tmp.clean(new_val); + if (tmp.type == 'list' && new_val != '') new_val = $(el).data('selected'); + } + if (el.type == 'checkbox') new_val = el.checked; + // change/restore event + var eventData = { + phase: 'before', type: 'change', target: this.name, input_id: el.id, recid: rec.recid, index: index, column: column, + value_new: new_val, value_previous: (rec.changes && rec.changes.hasOwnProperty(col.field) ? rec.changes[col.field]: old_val), value_original: old_val + }; + while (true) { + new_val = eventData.value_new; + if (( typeof old_val == 'undefined' || old_val === null ? '' : String(old_val)) !== String(new_val)) { + // change event + eventData = this.trigger($.extend(eventData, { type: 'change', phase: 'before' })); + if (eventData.isCancelled !== true) { + if (new_val !== eventData.value_new) { + // re-evaluate the type of change to be made + continue; + } + // default action + rec.changes = rec.changes || {}; + rec.changes[col.field] = eventData.value_new; + // event after + this.trigger($.extend(eventData, { phase: 'after' })); + } + } else { + // restore event + eventData = this.trigger($.extend(eventData, { type: 'restore', phase: 'before' })); + if (eventData.isCancelled !== true) { + if (new_val !== eventData.value_new) { + // re-evaluate the type of change to be made + continue; + } + // default action + if (rec.changes) delete rec.changes[col.field]; + if ($.isEmptyObject(rec.changes)) delete rec.changes; + // event after + this.trigger($.extend(eventData, { phase: 'after' })); + } + } + break; + } + // refresh cell + var cell = this.getCellHTML(index, column, summary); + if (!summary) { + if (rec.changes && typeof rec.changes[col.field] != 'undefined') { + $(tr).find('[col='+ column +']').addClass('w2ui-changed').html(cell); + } else { + $(tr).find('[col='+ column +']').removeClass('w2ui-changed').html(cell); + } + } + }, + + "delete": function (force) { + var obj = this; + // event before + var eventData = this.trigger({ phase: 'before', target: this.name, type: 'delete', force: force }); + if (eventData.isCancelled === true) return; + force = eventData.force; + // default action + var recs = this.getSelection(); + if (recs.length == 0) return; + if (this.msgDelete != '' && !force) { + w2confirm({ + title : w2utils.lang('Delete Confirmation'), + msg : obj.msgDelete, + btn_yes : { "class": 'btn-red' }, + callBack: function (result) { + if (result == 'Yes') w2ui[obj.name].delete(true); + } + }); + return; + } + // call delete script + var url = (typeof this.url != 'object' ? this.url : this.url.remove); + if (url) { + this.request('delete-records'); + } else { + this.selectNone(); + if (typeof recs[0] != 'object') { + this.remove.apply(this, recs); + } else { + // clear cells + for (var r in recs) { + var fld = this.columns[recs[r].column].field; + var ind = this.get(recs[r].recid, true); + if (ind != null && fld != 'recid') { + this.records[ind][fld] = ''; + if (this.records[ind].changes) delete this.records[ind].changes[fld]; + } + } + this.refresh(); + } + } + // event after + this.trigger($.extend(eventData, { phase: 'after' })); + }, + + click: function (recid, event) { + var time = (new Date()).getTime(); + var column = null; + if (this.last.cancelClick == true || (event && event.altKey)) return; + if (typeof recid == 'object') { + column = recid.column; + recid = recid.recid; + } + if (typeof event == 'undefined') event = {}; + // check for double click + if (time - parseInt(this.last.click_time) < 350 && event.type == 'click') { + this.dblClick(recid, event); + return; + } + this.last.click_time = time; + // column user clicked on + if (column == null && event.target) { + var tmp = event.target; + if (tmp.tagName != 'TD') tmp = $(tmp).parents('td')[0]; + if (typeof $(tmp).attr('col') != 'undefined') column = parseInt($(tmp).attr('col')); + } + // event before + var eventData = this.trigger({ phase: 'before', target: this.name, type: 'click', recid: recid, column: column, originalEvent: event }); + if (eventData.isCancelled === true) return; + // if it is subgrid unselect top grid + var parent = $('#grid_'+ this.name +'_rec_'+ w2utils.escapeId(recid)).parents('tr'); + if (parent.length > 0 && String(parent.attr('id')).indexOf('expanded_row') != -1) { + var grid = parent.parents('.w2ui-grid').attr('name'); + w2ui[grid].selectNone(); + // all subgrids + parent.parents('.w2ui-grid').find('.w2ui-expanded-row .w2ui-grid').each(function (index, el) { + var grid = $(el).attr('name'); + if (w2ui[grid]) w2ui[grid].selectNone(); + }); + } + // unselect all subgrids + $(this.box).find('.w2ui-expanded-row .w2ui-grid').each(function (index, el) { + var grid = $(el).attr('name'); + if (w2ui[grid]) w2ui[grid].selectNone(); + }); + // default action + var obj = this; + var sel = this.getSelection(); + $('#grid_'+ this.name +'_check_all').prop("checked", false); + var ind = this.get(recid, true); + var record = this.records[ind]; + var selectColumns = []; + obj.last.sel_ind = ind; + obj.last.sel_col = column; + obj.last.sel_recid = recid; + obj.last.sel_type = 'click'; + // multi select with shif key + if (event.shiftKey && sel.length > 0 && obj.multiSelect) { + if (sel[0].recid) { + var start = this.get(sel[0].recid, true); + var end = this.get(recid, true); + if (column > sel[0].column) { + var t1 = sel[0].column; + var t2 = column; + } else { + var t1 = column; + var t2 = sel[0].column; + } + for (var c = t1; c <= t2; c++) selectColumns.push(c); + } else { + var start = this.get(sel[0], true); + var end = this.get(recid, true); + } + var sel_add = [] + if (start > end) { var tmp = start; start = end; end = tmp; } + var url = (typeof this.url != 'object' ? this.url : this.url.get); + for (var i = start; i <= end; i++) { + if (this.searchData.length > 0 && !url && $.inArray(i, this.last.searchIds) == -1) continue; + if (this.selectType == 'row') { + sel_add.push(this.records[i].recid); + } else { + for (var sc in selectColumns) sel_add.push({ recid: this.records[i].recid, column: selectColumns[sc] }); + } + //sel.push(this.records[i].recid); + } + this.select.apply(this, sel_add); + } else { + var last = this.last.selection; + var flag = (last.indexes.indexOf(ind) != -1 ? true : false); + // clear other if necessary + if (((!event.ctrlKey && !event.shiftKey && !event.metaKey) || !this.multiSelect) && !this.showSelectColumn) { + if (this.selectType != 'row' && $.inArray(column, last.columns[ind]) == -1) flag = false; + if (sel.length > 300) this.selectNone(); else this.unselect.apply(this, sel); + if (flag === true) { + this.unselect({ recid: recid, column: column }); + } else { + this.select({ recid: recid, column: column }); + } + } else { + if (this.selectType != 'row' && $.inArray(column, last.columns[ind]) == -1) flag = false; + if (flag === true) { + this.unselect({ recid: recid, column: column }); + } else { + this.select({ recid: recid, column: column }); + } + } + } + this.status(); + obj.initResize(); + // event after + this.trigger($.extend(eventData, { phase: 'after' })); + }, + + columnClick: function (field, event) { + // event before + var eventData = this.trigger({ phase: 'before', type: 'columnClick', target: this.name, field: field, originalEvent: event }); + if (eventData.isCancelled === true) return; + // default behaviour + var column = this.getColumn(field); + if (column.sortable) this.sort(field, null, (event && (event.ctrlKey || event.metaKey) ? true : false) ); + // event after + this.trigger($.extend(eventData, { phase: 'after' })); + }, + + keydown: function (event) { + // this method is called from w2utils + var obj = this; + if (obj.keyboard !== true) return; + // trigger event + var eventData = obj.trigger({ phase: 'before', type: 'keydown', target: obj.name, originalEvent: event }); + if (eventData.isCancelled === true) return; + // default behavior + var empty = false; + var records = $('#grid_'+ obj.name +'_records'); + var sel = obj.getSelection(); + if (sel.length == 0) empty = true; + var recid = sel[0] || null; + var columns = []; + var recid2 = sel[sel.length-1]; + if (typeof recid == 'object' && recid != null) { + recid = sel[0].recid; + columns = []; + var ii = 0; + while (true) { + if (!sel[ii] || sel[ii].recid != recid) break; + columns.push(sel[ii].column); + ii++; + } + recid2 = sel[sel.length-1].recid; + } + var ind = obj.get(recid, true); + var ind2 = obj.get(recid2, true); + var rec = obj.get(recid); + var recEL = $('#grid_'+ obj.name +'_rec_'+ (ind !== null ? w2utils.escapeId(obj.records[ind].recid) : 'none')); + var cancel = false; + var key = event.keyCode; + var shiftKey= event.shiftKey; + if (key == 9) { // tab key + if (event.shiftKey) key = 37; else key = 39; // replace with arrows + shiftKey = false; + cancel = true; + } + switch (key) { + case 8: // backspace + case 46: // delete + if (this.show.toolbarDelete) obj["delete"](); + cancel = true; + event.stopPropagation(); + break; + + case 27: // escape + obj.selectNone(); + if (sel.length > 0 && typeof sel[0] == 'object') { + obj.select({ recid: sel[0].recid, column: sel[0].column }); + } + cancel = true; + break; + + case 65: // cmd + A + if (!event.metaKey && !event.ctrlKey) break; + obj.selectAll(); + cancel = true; + break; + + case 70: // cmd + F + if (!event.metaKey && !event.ctrlKey) break; + $('#grid_'+ obj.name + '_search_all').focus(); + cancel = true; + break; + + case 13: // enter + // if expandable columns - expand it + if (this.selectType == 'row' && obj.show.expandColumn === true) { + if (recEL.length <= 0) break; + obj.toggle(recid, event); + cancel = true; + } else { // or enter edit + for (var c in this.columns) { + if (this.columns[c].editable) { + columns.push(parseInt(c)); + break; + } + } + // edit last column that was edited + if (this.selectType == 'row' && this.last.edit_col) columns = [this.last.edit_col]; + if (columns.length > 0) { + obj.editField(recid, columns[0], null, event); + cancel = true; + } + } + break; + + case 37: // left + if (empty) break; + // check if this is subgrid + var parent = $('#grid_'+ this.name +'_rec_'+ w2utils.escapeId(obj.records[ind].recid)).parents('tr'); + if (parent.length > 0 && String(parent.attr('id')).indexOf('expanded_row') != -1) { + var recid = parent.prev().attr('recid'); + var grid = parent.parents('.w2ui-grid').attr('name'); + obj.selectNone(); + w2utils.keyboard.active(grid); + w2ui[grid].set(recid, { expanded: false }); + w2ui[grid].collapse(recid); + w2ui[grid].click(recid); + cancel = true; + break; + } + if (this.selectType == 'row') { + if (recEL.length <= 0 || rec.expanded !== true ) break; + obj.set(recid, { expanded: false }, true); + obj.collapse(recid, event); + } else { + var prev = obj.prevCell(columns[0]); + if (prev != null) { + if (shiftKey && obj.multiSelect) { + if (tmpUnselect()) return; + var tmp = []; + var newSel = []; + var unSel = []; + if (columns.indexOf(this.last.sel_col) == 0 && columns.length > 1) { + for (var i in sel) { + if (tmp.indexOf(sel[i].recid) == -1) tmp.push(sel[i].recid); + unSel.push({ recid: sel[i].recid, column: columns[columns.length-1] }); + } + } else { + for (var i in sel) { + if (tmp.indexOf(sel[i].recid) == -1) tmp.push(sel[i].recid); + newSel.push({ recid: sel[i].recid, column: prev }); + } + } + obj.unselect.apply(obj, unSel); + obj.select.apply(obj, newSel); + } else { + event.shiftKey = false; + obj.click({ recid: recid, column: prev }, event); + } + } else { + // if selected more then one, then select first + if (!shiftKey) { + for (var s=1; s<sel.length; s++) obj.unselect(sel[s]); + } + } + } + cancel = true; + break; + + case 39: // right + if (empty) break; + if (this.selectType == 'row') { + if (recEL.length <= 0 || rec.expanded === true || obj.show.expandColumn !== true) break; + obj.expand(recid, event); + } else { + var next = obj.nextCell(columns[columns.length-1]); + if (next !== null) { + if (shiftKey && key == 39 && obj.multiSelect) { + if (tmpUnselect()) return; + var tmp = []; + var newSel = []; + var unSel = []; + if (columns.indexOf(this.last.sel_col) == columns.length-1 && columns.length > 1) { + for (var i in sel) { + if (tmp.indexOf(sel[i].recid) == -1) tmp.push(sel[i].recid); + unSel.push({ recid: sel[i].recid, column: columns[0] }); + } + } else { + for (var i in sel) { + if (tmp.indexOf(sel[i].recid) == -1) tmp.push(sel[i].recid); + newSel.push({ recid: sel[i].recid, column: next }); + } + } + obj.unselect.apply(obj, unSel); + obj.select.apply(obj, newSel); + } else { + obj.click({ recid: recid, column: next }, event); + } + } else { + // if selected more then one, then select first + if (!shiftKey) { + for (var s=0; s<sel.length-1; s++) obj.unselect(sel[s]); + } + } + } + cancel = true; + break; + + case 38: // up + if (empty) selectTopRecord(); + if (recEL.length <= 0) break; + // move to the previous record + var prev = obj.prevRow(ind); + if (prev != null) { + // jump into subgrid + if (obj.records[prev].expanded) { + var subgrid = $('#grid_'+ obj.name +'_rec_'+ w2utils.escapeId(obj.records[prev].recid) +'_expanded_row').find('.w2ui-grid'); + if (subgrid.length > 0 && w2ui[subgrid.attr('name')]) { + obj.selectNone(); + var grid = subgrid.attr('name'); + var recs = w2ui[grid].records; + w2utils.keyboard.active(grid); + w2ui[grid].click(recs[recs.length-1].recid); + cancel = true; + break; + } + } + if (shiftKey && obj.multiSelect) { // expand selection + if (tmpUnselect()) return; + if (obj.selectType == 'row') { + if (obj.last.sel_ind > prev && obj.last.sel_ind != ind2) { + obj.unselect(obj.records[ind2].recid); + } else { + obj.select(obj.records[prev].recid); + } + } else { + if (obj.last.sel_ind > prev && obj.last.sel_ind != ind2) { + prev = ind2; + var tmp = []; + for (var c in columns) tmp.push({ recid: obj.records[prev].recid, column: columns[c] }); + obj.unselect.apply(obj, tmp); + } else { + var tmp = []; + for (var c in columns) tmp.push({ recid: obj.records[prev].recid, column: columns[c] }); + obj.select.apply(obj, tmp); + } + } + } else { // move selected record + obj.selectNone(); + obj.click({ recid: obj.records[prev].recid, column: columns[0] }, event); + } + obj.scrollIntoView(prev); + if (event.preventDefault) event.preventDefault(); + } else { + // if selected more then one, then select first + if (!shiftKey) { + for (var s=1; s<sel.length; s++) obj.unselect(sel[s]); + } + // jump out of subgird (if first record) + var parent = $('#grid_'+ obj.name +'_rec_'+ w2utils.escapeId(obj.records[ind].recid)).parents('tr'); + if (parent.length > 0 && String(parent.attr('id')).indexOf('expanded_row') != -1) { + var recid = parent.prev().attr('recid'); + var grid = parent.parents('.w2ui-grid').attr('name'); + obj.selectNone(); + w2utils.keyboard.active(grid); + w2ui[grid].click(recid); + cancel = true; + break; + } + } + break; + + case 40: // down + if (empty) selectTopRecord(); + if (recEL.length <= 0) break; + // jump into subgrid + if (obj.records[ind2].expanded) { + var subgrid = $('#grid_'+ this.name +'_rec_'+ w2utils.escapeId(obj.records[ind2].recid) +'_expanded_row').find('.w2ui-grid'); + if (subgrid.length > 0 && w2ui[subgrid.attr('name')]) { + obj.selectNone(); + var grid = subgrid.attr('name'); + var recs = w2ui[grid].records; + w2utils.keyboard.active(grid); + w2ui[grid].click(recs[0].recid); + cancel = true; + break; + } + } + // move to the next record + var next = obj.nextRow(ind2); + if (next != null) { + if (shiftKey && obj.multiSelect) { // expand selection + if (tmpUnselect()) return; + if (obj.selectType == 'row') { + if (this.last.sel_ind < next && this.last.sel_ind != ind) { + obj.unselect(obj.records[ind].recid); + } else { + obj.select(obj.records[next].recid); + } + } else { + if (this.last.sel_ind < next && this.last.sel_ind != ind) { + next = ind; + var tmp = []; + for (var c in columns) tmp.push({ recid: obj.records[next].recid, column: columns[c] }); + obj.unselect.apply(obj, tmp); + } else { + var tmp = []; + for (var c in columns) tmp.push({ recid: obj.records[next].recid, column: columns[c] }); + obj.select.apply(obj, tmp); + } + } + } else { // move selected record + obj.selectNone(); + obj.click({ recid: obj.records[next].recid, column: columns[0] }, event); + } + obj.scrollIntoView(next); + cancel = true; + } else { + // if selected more then one, then select first + if (!shiftKey) { + for (var s=0; s<sel.length-1; s++) obj.unselect(sel[s]); + } + // jump out of subgrid (if last record in subgrid) + var parent = $('#grid_'+ this.name +'_rec_'+ w2utils.escapeId(obj.records[ind2].recid)).parents('tr'); + if (parent.length > 0 && String(parent.attr('id')).indexOf('expanded_row') != -1) { + var recid = parent.next().attr('recid'); + var grid = parent.parents('.w2ui-grid').attr('name'); + obj.selectNone(); + w2utils.keyboard.active(grid); + w2ui[grid].click(recid); + cancel = true; + break; + } + } + break; + + // copy & paste + + case 17: // ctrl key + case 91: // cmd key + if (empty) break; + var text = obj.copy(); + $('body').append('<textarea id="_tmp_copy_data" '+ + ' onpaste="var obj = this; setTimeout(function () { w2ui[\''+ obj.name + '\'].paste(obj.value); }, 1);" '+ + ' onkeydown="w2ui[\''+ obj.name +'\'].keydown(event)"'+ + ' style="position: absolute; top: -100px; height: 1px; width: 1px">'+ text +'</textarea>'); + $('#_tmp_copy_data').focus().select(); + // remove _tmp_copy_data textarea + $(document).on('keyup', tmp_key_down); + function tmp_key_down() { + $('#_tmp_copy_data').remove(); + $(document).off('keyup', tmp_key_down); + } + break; + + case 88: // x - cut + if (empty) break; + if (event.ctrlKey || event.metaKey) { + setTimeout(function () { obj["delete"](true); }, 100); + } + break; + } + var tmp = [187, 189, 32]; // =-spacebar + for (var i=48; i<=90; i++) tmp.push(i); // 0-9,a-z,A-Z + if (tmp.indexOf(key) != -1 && !event.ctrlKey && !event.metaKey && !cancel) { + if (columns.length == 0) columns.push(0); + var tmp = String.fromCharCode(key); + if (key == 187) tmp = '='; + if (key == 189) tmp = '-'; + if (!shiftKey) tmp = tmp.toLowerCase(); + obj.editField(recid, columns[0], tmp, event); + cancel = true; + } + if (cancel) { // cancel default behaviour + if (event.preventDefault) event.preventDefault(); + } + // event after + obj.trigger($.extend(eventData, { phase: 'after' })); + + function selectTopRecord() { + var ind = Math.floor((records[0].scrollTop + (records.height() / 2.1)) / obj.recordHeight); + if (!obj.records[ind]) ind = 0; + obj.select({ recid: obj.records[ind].recid, column: 0}); + } + + function tmpUnselect () { + if (obj.last.sel_type != 'click') return false; + if (obj.selectType != 'row') { + obj.last.sel_type = 'key'; + if (sel.length > 1) { + for (var s in sel) { + if (sel[s].recid == obj.last.sel_recid && sel[s].column == obj.last.sel_col) { + sel.splice(s, 1); + break; + } + } + obj.unselect.apply(obj, sel); + return true; + } + return false; + } else { + obj.last.sel_type = 'key'; + if (sel.length > 1) { + sel.splice(sel.indexOf(obj.records[obj.last.sel_ind].recid), 1); + obj.unselect.apply(obj, sel); + return true; + } + return false; + } + } + }, + + scrollIntoView: function (ind) { + var buffered = this.records.length; + if (this.searchData.length != 0 && !this.url) buffered = this.last.searchIds.length; + if (typeof ind == 'undefined') { + var sel = this.getSelection(); + if (sel.length == 0) return; + ind = this.get(sel[0], true); + } + var records = $('#grid_'+ this.name +'_records'); + if (buffered == 0) return; + // if all records in view + var len = this.last.searchIds.length; + if (records.height() > this.recordHeight * (len > 0 ? len : buffered)) return; + if (len > 0) ind = this.last.searchIds.indexOf(ind); // if seach is applied + // scroll to correct one + var t1 = Math.floor(records[0].scrollTop / this.recordHeight); + var t2 = t1 + Math.floor(records.height() / this.recordHeight); + if (ind == t1) records.animate({ 'scrollTop': records.scrollTop() - records.height() / 1.3 }, 250, 'linear'); + if (ind == t2) records.animate({ 'scrollTop': records.scrollTop() + records.height() / 1.3 }, 250, 'linear'); + if (ind < t1 || ind > t2) records.animate({ 'scrollTop': (ind - 1) * this.recordHeight }); + }, + + dblClick: function (recid, event) { + //if (window.getSelection) window.getSelection().removeAllRanges(); // clear selection + // find columns + var column = null; + if (typeof recid == 'object') { + column = recid.column; + recid = recid.recid; + } + if (typeof event == 'undefined') event = {}; + // column user clicked on + if (column == null && event.target) { + var tmp = event.target; + if (tmp.tagName != 'TD') tmp = $(tmp).parents('td')[0]; + column = parseInt($(tmp).attr('col')); + } + // event before + var eventData = this.trigger({ phase: 'before', target: this.name, type: 'dblClick', recid: recid, column: column, originalEvent: event }); + if (eventData.isCancelled === true) return; + // default action + this.selectNone(); + var col = this.columns[column]; + if (col && $.isPlainObject(col.editable)) { + this.editField(recid, column, null, event); + } else { + this.select({ recid: recid, column: column }); + } + // event after + this.trigger($.extend(eventData, { phase: 'after' })); + }, + + contextMenu: function (recid, event) { + var obj = this; + if (obj.last.userSelect == 'text') return; + if (typeof event == 'undefined') event = { offsetX: 0, offsetY: 0, target: $('#grid_'+ obj.name +'_rec_'+ recid)[0] }; + if (typeof event.offsetX === 'undefined') { + event.offsetX = event.layerX - event.target.offsetLeft; + event.offsetY = event.layerY - event.target.offsetTop; + } + if (w2utils.isFloat(recid)) recid = parseFloat(recid); + if (this.getSelection().indexOf(recid) == -1) obj.click(recid); + // need timeout to allow click to finish first + setTimeout(function () { + // event before + var eventData = obj.trigger({ phase: 'before', type: 'contextMenu', target: obj.name, originalEvent: event, recid: recid }); + if (eventData.isCancelled === true) return; + // default action + if (obj.menu.length > 0) { + $(obj.box).find(event.target) + .w2menu(obj.menu, { + left : event.offsetX, + onSelect: function (event) { + obj.menuClick(recid, parseInt(event.index), event.originalEvent); + } + } + ); + } + // event after + obj.trigger($.extend(eventData, { phase: 'after' })); + }, 150); // need timer 150 for FF + // cancel event + if (event.preventDefault) event.preventDefault(); + }, + + menuClick: function (recid, index, event) { + var obj = this; + // event before + var eventData = obj.trigger({ phase: 'before', type: 'menuClick', target: obj.name, originalEvent: event, + recid: recid, menuIndex: index, menuItem: obj.menu[index] }); + if (eventData.isCancelled === true) return; + // default action + // -- empty + // event after + obj.trigger($.extend(eventData, { phase: 'after' })); + }, + + toggle: function (recid) { + var rec = this.get(recid); + if (rec.expanded === true) return this.collapse(recid); else return this.expand(recid); + }, + + expand: function (recid) { + var rec = this.get(recid); + var obj = this; + var id = w2utils.escapeId(recid); + if ($('#grid_'+ this.name +'_rec_'+ id +'_expanded_row').length > 0) return false; + if (rec.expanded == 'none') return false; + // insert expand row + var tmp = 1 + (this.show.selectColumn ? 1 : 0); + var addClass = ''; // ($('#grid_'+this.name +'_rec_'+ w2utils.escapeId(recid)).hasClass('w2ui-odd') ? 'w2ui-odd' : 'w2ui-even'); + $('#grid_'+ this.name +'_rec_'+ id).after( + '<tr id="grid_'+ this.name +'_rec_'+ id +'_expanded_row" class="w2ui-expanded-row '+ addClass +'">'+ + (this.show.lineNumbers ? '<td class="w2ui-col-number"></td>' : '') + + ' <td class="w2ui-grid-data w2ui-expanded1" colspan="'+ tmp +'"><div style="display: none"></div></td>'+ + ' <td colspan="100" class="w2ui-expanded2">'+ + ' <div id="grid_'+ this.name +'_rec_'+ id +'_expanded" style="opacity: 0"></div>'+ + ' </td>'+ + '</tr>'); + // event before + var eventData = this.trigger({ phase: 'before', type: 'expand', target: this.name, recid: recid, + box_id: 'grid_'+ this.name +'_rec_'+ id +'_expanded', ready: ready }); + if (eventData.isCancelled === true) { + $('#grid_'+ this.name +'_rec_'+ id +'_expanded_row').remove(); + return; + } + // default action + $('#grid_'+ this.name +'_rec_'+ id).attr('expanded', 'yes').addClass('w2ui-expanded'); + $('#grid_'+ this.name +'_rec_'+ id +'_expanded_row').show(); + $('#grid_'+ this.name +'_cell_'+ this.get(recid, true) +'_expand div').html('<div class="w2ui-spinner" style="width: 16px; height: 16px; margin: -2px 2px;"></div>'); + rec.expanded = true; + // check if height of expanded row > 5 then remove spinner + setTimeout(ready, 300); + function ready() { + var div1 = $('#grid_'+ obj.name +'_rec_'+ id +'_expanded'); + var div2 = $('#grid_'+ obj.name +'_rec_'+ id +'_expanded_row .w2ui-expanded1 > div'); + if (div1.height() < 5) return; + div1.css('opacity', 1); + div2.show().css('opacity', 1); + $('#grid_'+ obj.name +'_cell_'+ obj.get(recid, true) +'_expand div').html('-'); + } + // event after + this.trigger($.extend(eventData, { phase: 'after' })); + this.resizeRecords(); + return true; + }, + + collapse: function (recid) { + var rec = this.get(recid); + var obj = this; + var id = w2utils.escapeId(recid); + if ($('#grid_'+ this.name +'_rec_'+ id +'_expanded_row').length == 0) return false; + // event before + var eventData = this.trigger({ phase: 'before', type: 'collapse', target: this.name, recid: recid, + box_id: 'grid_'+ this.name +'_rec_'+ id +'_expanded' }); + if (eventData.isCancelled === true) return; + // default action + $('#grid_'+ this.name +'_rec_'+ id).removeAttr('expanded').removeClass('w2ui-expanded'); + $('#grid_'+ this.name +'_rec_'+ id +'_expanded').css('opacity', 0); + $('#grid_'+ this.name +'_cell_'+ this.get(recid, true) +'_expand div').html('+'); + setTimeout(function () { + $('#grid_'+ obj.name +'_rec_'+ id +'_expanded').height('0px'); + setTimeout(function () { + $('#grid_'+ obj.name +'_rec_'+ id +'_expanded_row').remove(); + delete rec.expanded; + // event after + obj.trigger($.extend(eventData, { phase: 'after' })); + obj.resizeRecords(); + }, 300); + }, 200); + return true; + }, + + sort: function (field, direction, multiField) { // if no params - clears sort + // event before + var eventData = this.trigger({ phase: 'before', type: 'sort', target: this.name, field: field, direction: direction, multiField: multiField }); + if (eventData.isCancelled === true) return; + // check if needed to quit + if (typeof field != 'undefined') { + // default action + var sortIndex = this.sortData.length; + for (var s in this.sortData) { + if (this.sortData[s].field == field) { sortIndex = s; break; } + } + if (typeof direction == 'undefined' || direction == null) { + if (typeof this.sortData[sortIndex] == 'undefined') { + direction = 'asc'; + } else { + switch (String(this.sortData[sortIndex].direction)) { + case 'asc' : direction = 'desc'; break; + case 'desc' : direction = 'asc'; break; + default : direction = 'asc'; break; + } + } + } + if (this.multiSort === false) { this.sortData = []; sortIndex = 0; } + if (multiField != true) { this.sortData = []; sortIndex = 0; } + // set new sort + if (typeof this.sortData[sortIndex] == 'undefined') this.sortData[sortIndex] = {}; + this.sortData[sortIndex].field = field; + this.sortData[sortIndex].direction = direction; + } else { + this.sortData = []; + } + this.selectNone(); + // if local + var url = (typeof this.url != 'object' ? this.url : this.url.get); + if (!url) { + this.localSort(); + if (this.searchData.length > 0) this.localSearch(true); + // event after + this.trigger($.extend(eventData, { phase: 'after' })); + this.refresh(); + } else { + // event after + this.trigger($.extend(eventData, { phase: 'after' })); + this.last.xhr_offset = 0; + this.reload(); + } + }, + + copy: function () { + var sel = this.getSelection(); + if (sel.length == 0) return ''; + var text = ''; + if (typeof sel[0] == 'object') { // cell copy + // find min/max column + var minCol = sel[0].column; + var maxCol = sel[0].column; + var recs = []; + for (var s in sel) { + if (sel[s].column < minCol) minCol = sel[s].column; + if (sel[s].column > maxCol) maxCol = sel[s].column; + if (recs.indexOf(sel[s].index) == -1) recs.push(sel[s].index); + } + recs.sort(); + for (var r in recs) { + var ind = recs[r]; + for (var c = minCol; c <= maxCol; c++) { + var col = this.columns[c]; + if (col.hidden === true) continue; + text += w2utils.stripTags(this.getCellHTML(ind, c)) + '\t'; + } + text = text.substr(0, text.length-1); // remove last \t + text += '\n'; + } + } else { // row copy + // copy headers + for (var c in this.columns) { + var col = this.columns[c]; + if (col.hidden === true) continue; + text += '"' + w2utils.stripTags(col.caption ? col.caption : col.field) + '"\t'; + } + text = text.substr(0, text.length-1); // remove last \t + text += '\n'; + // copy selected text + for (var s in sel) { + var ind = this.get(sel[s], true); + for (var c in this.columns) { + var col = this.columns[c]; + if (col.hidden === true) continue; + text += '"' + w2utils.stripTags(this.getCellHTML(ind, c)) + '"\t'; + } + text = text.substr(0, text.length-1); // remove last \t + text += '\n'; + } + } + text = text.substr(0, text.length - 1); + // before event + var eventData = this.trigger({ phase: 'before', type: 'copy', target: this.name, text: text }); + if (eventData.isCancelled === true) return ''; + text = eventData.text; + // event after + this.trigger($.extend(eventData, { phase: 'after' })); + return text; + }, + + paste: function (text) { + var sel = this.getSelection(); + var ind = this.get(sel[0].recid, true); + var col = sel[0].column; + // before event + var eventData = this.trigger({ phase: 'before', type: 'paste', target: this.name, text: text, index: ind, column: col }); + if (eventData.isCancelled === true) return; + text = eventData.text; + // default action + if (this.selectType == 'row' || sel.length == 0) { + console.log('ERROR: You can paste only if grid.selectType = \'cell\' and when at least one cell selected.'); + // event after + this.trigger($.extend(eventData, { phase: 'after' })); + return; + } + var newSel = []; + var text = text.split('\n'); + for (var t in text) { + var tmp = text[t].split('\t'); + var cnt = 0; + var rec = this.records[ind]; + var cols = []; + for (var dt in tmp) { + if (!this.columns[col + cnt]) continue; + var field = this.columns[col + cnt].field; + rec.changes = rec.changes || {}; + rec.changes[field] = tmp[dt]; + cols.push(col + cnt); + cnt++; + } + for (var c in cols) newSel.push({ recid: rec.recid, column: cols[c] }); + ind++; + } + this.selectNone(); + this.select.apply(this, newSel); + this.refresh(); + // event after + this.trigger($.extend(eventData, { phase: 'after' })); + }, + + // ================================================== + // --- Common functions + + resize: function () { + var obj = this; + var time = (new Date()).getTime(); + //if (window.getSelection) window.getSelection().removeAllRanges(); // clear selection + // make sure the box is right + if (!this.box || $(this.box).attr('name') != this.name) return; + // determine new width and height + $(this.box).find('> div') + .css('width', $(this.box).width()) + .css('height', $(this.box).height()); + // event before + var eventData = this.trigger({ phase: 'before', type: 'resize', target: this.name }); + if (eventData.isCancelled === true) return; + // resize + obj.resizeBoxes(); + obj.resizeRecords(); + // event after + this.trigger($.extend(eventData, { phase: 'after' })); + return (new Date()).getTime() - time; + }, + + refreshCell: function (recid, field) { + var index = this.get(recid, true); + var col_ind = this.getColumn(field, true); + var rec = this.records[index]; + var col = this.columns[col_ind]; + var cell = $('#grid_'+ this.name + '_rec_'+ recid +' [col='+ col_ind +']'); + // set cell html and changed flag + cell.html(this.getCellHTML(index, col_ind)); + if (rec.changes && typeof rec.changes[col.field] != 'undefined') { + cell.addClass('w2ui-changed'); + } else { + cell.removeClass('w2ui-changed'); + } + }, + + refreshRow: function (recid) { + var tr = $('#grid_'+ this.name +'_rec_'+ w2utils.escapeId(recid)); + if (tr.length != 0) { + var ind = this.get(recid, true); + var line = tr.attr('line'); + // if it is searched, find index in search array + var url = (typeof this.url != 'object' ? this.url : this.url.get); + if (this.searchData.length > 0 && !url) for (var s in this.last.searchIds) if (this.last.searchIds[s] == ind) ind = s; + $(tr).replaceWith(this.getRecordHTML(ind, line)); + } + + }, + + refresh: function () { + var obj = this; + var time = (new Date()).getTime(); + var url = (typeof this.url != 'object' ? this.url : this.url.get); + if (this.total <= 0 && !url && this.searchData.length == 0) { + this.total = this.records.length; + } + //if (window.getSelection) window.getSelection().removeAllRanges(); // clear selection + this.toolbar.disable('w2ui-edit', 'w2ui-delete'); + if (!this.box) return; + // event before + var eventData = this.trigger({ phase: 'before', target: this.name, type: 'refresh' }); + if (eventData.isCancelled === true) return; + // -- header + if (this.show.header) { + $('#grid_'+ this.name +'_header').html(this.header +' ').show(); + } else { + $('#grid_'+ this.name +'_header').hide(); + } + // -- toolbar + if (this.show.toolbar) { + // if select-collumn is checked - no toolbar refresh + if (this.toolbar && this.toolbar.get('w2ui-column-on-off') && this.toolbar.get('w2ui-column-on-off').checked) { + // no action + } else { + $('#grid_'+ this.name +'_toolbar').show(); + // refresh toolbar all but search field + if (typeof this.toolbar == 'object') { + var tmp = this.toolbar.items; + for (var t in tmp) { + if (tmp[t].id == 'w2ui-search' || tmp[t].type == 'break') continue; + this.toolbar.refresh(tmp[t].id); + } + } + } + } else { + $('#grid_'+ this.name +'_toolbar').hide(); + } + // -- make sure search is closed + this.searchClose(); + // search placeholder + var el = $('#grid_'+ obj.name +'_search_all'); + if (!this.multiSearch && this.last.field == 'all' && this.searches.length > 0) { + this.last.field = this.searches[0].field; + this.last.caption = this.searches[0].caption; + } + for (var s in this.searches) { + if (this.searches[s].field == this.last.field) this.last.caption = this.searches[s].caption; + } + if (this.last.multi) { + el.attr('placeholder', '[' + w2utils.lang('Multiple Fields') + ']'); + } else { + el.attr('placeholder', this.last.caption); + } + if (el.val() != this.last.search) { + var val = this.last.search; + var tmp = el.data('w2field'); + if (tmp) val = tmp.format(val); + el.val(val); + } + + // -- separate summary + var tmp = this.find({ summary: true }, true); + if (tmp.length > 0) { + for (var t in tmp) this.summary.push(this.records[tmp[t]]); + for (var t=tmp.length-1; t>=0; t--) this.records.splice(tmp[t], 1); + this.total = this.total - tmp.length; + } + + // -- body + var bodyHTML = ''; + bodyHTML += '<div id="grid_'+ this.name +'_records" class="w2ui-grid-records"'+ + ' onscroll="var obj = w2ui[\''+ this.name + '\']; '+ + ' obj.last.scrollTop = this.scrollTop; '+ + ' obj.last.scrollLeft = this.scrollLeft; '+ + ' $(\'#grid_'+ this.name +'_columns\')[0].scrollLeft = this.scrollLeft;'+ + ' $(\'#grid_'+ this.name +'_summary\')[0].scrollLeft = this.scrollLeft;'+ + ' obj.scroll(event);">'+ + this.getRecordsHTML() + + '</div>'+ + '<div id="grid_'+ this.name +'_columns" class="w2ui-grid-columns">'+ + ' <table>'+ this.getColumnsHTML() +'</table>'+ + '</div>'; // Columns need to be after to be able to overlap + $('#grid_'+ this.name +'_body').html(bodyHTML); + // show summary records + if (this.summary.length > 0) { + $('#grid_'+ this.name +'_summary').html(this.getSummaryHTML()).show(); + } else { + $('#grid_'+ this.name +'_summary').hide(); + } + // -- footer + if (this.show.footer) { + $('#grid_'+ this.name +'_footer').html(this.getFooterHTML()).show(); + } else { + $('#grid_'+ this.name +'_footer').hide(); + } + // show/hide clear search link + if (this.searchData.length > 0) { + $('#grid_'+ this.name +'_searchClear').show(); + } else { + $('#grid_'+ this.name +'_searchClear').hide(); + } + // all selected? + var sel = this.last.selection; + if (sel.indexes.length == this.records.length || (this.searchData.length !== 0 && sel.indexes.length == this.last.searchIds.length)) { + $('#grid_'+ this.name +'_check_all').prop('checked', true); + } else { + $('#grid_'+ this.name +'_check_all').prop('checked', false); + } + // show number of selected + this.status(); + // collapse all records + var rows = obj.find({ expanded: true }, true); + for (var r in rows) obj.records[rows[r]].expanded = false; + // mark selection + setTimeout(function () { + var str = $.trim($('#grid_'+ obj.name +'_search_all').val()); + if (str != '') $(obj.box).find('.w2ui-grid-data > div').w2marker(str); + }, 50); + // event after + this.trigger($.extend(eventData, { phase: 'after' })); + obj.resize(); + obj.addRange('selection'); + setTimeout(function () { obj.resize(); obj.scroll(); }, 1); // allow to render first + + if ( obj.reorderColumns && !obj.last.columnDrag ) { + obj.last.columnDrag = obj.initColumnDrag(); + } else if ( !obj.reorderColumns && obj.last.columnDrag ) { + obj.last.columnDrag.remove(); + } + + return (new Date()).getTime() - time; + }, + + render: function (box) { + var obj = this; + var time = (new Date()).getTime(); + //if (window.getSelection) window.getSelection().removeAllRanges(); // clear selection + if (typeof box != 'undefined' && box != null) { + if ($(this.box).find('#grid_'+ this.name +'_body').length > 0) { + $(this.box) + .removeAttr('name') + .removeClass('w2ui-reset w2ui-grid') + .html(''); + } + this.box = box; + } + if (!this.box) return; + if (this.last.sortData == null) this.last.sortData = this.sortData; + // event before + var eventData = this.trigger({ phase: 'before', target: this.name, type: 'render', box: box }); + if (eventData.isCancelled === true) return; + // insert Elements + $(this.box) + .attr('name', this.name) + .addClass('w2ui-reset w2ui-grid') + .html('<div>'+ + ' <div id="grid_'+ this.name +'_header" class="w2ui-grid-header"></div>'+ + ' <div id="grid_'+ this.name +'_toolbar" class="w2ui-grid-toolbar"></div>'+ + ' <div id="grid_'+ this.name +'_body" class="w2ui-grid-body"></div>'+ + ' <div id="grid_'+ this.name +'_summary" class="w2ui-grid-body w2ui-grid-summary"></div>'+ + ' <div id="grid_'+ this.name +'_footer" class="w2ui-grid-footer"></div>'+ + '</div>'); + if (this.selectType != 'row') $(this.box).addClass('w2ui-ss'); + if ($(this.box).length > 0) $(this.box)[0].style.cssText += this.style; + // init toolbar + this.initToolbar(); + if (this.toolbar != null) this.toolbar.render($('#grid_'+ this.name +'_toolbar')[0]); + // reinit search_all + if (this.last.field && this.last.field != 'all') { + var sd = this.searchData; + this.initAllField(this.last.field, (sd.length == 1 ? sd[0].value : null)); + } + // init footer + $('#grid_'+ this.name +'_footer').html(this.getFooterHTML()); + // refresh + if (!this.last.state) this.last.state = this.stateSave(true); // initial default state + this.stateRestore(); + if (this.url) this.refresh(); // show empty grid (need it) - should it be only for remote data source + this.reload(); + + // init mouse events for mouse selection + $(this.box).on('mousedown', mouseStart); + $(this.box).on('selectstart', function () { return false; }); // fixes chrome cursor bug + + // event after + this.trigger($.extend(eventData, { phase: 'after' })); + // attach to resize event + if ($('.w2ui-layout').length == 0) { // if there is layout, it will send a resize event + this.tmp_resize = function (event) { w2ui[obj.name].resize(); } + $(window).off('resize', this.tmp_resize).on('resize', this.tmp_resize); + } + return (new Date()).getTime() - time; + + function mouseStart (event) { + if (event.which != 1) return; // if not left mouse button + // restore css user-select + if (obj.last.userSelect == 'text') { + delete obj.last.userSelect; + $(obj.box).find('.w2ui-grid-body') + .css('user-select', 'none') + .css('-webkit-user-select', 'none') + .css('-moz-user-select', 'none') + .css('-ms-user-select', 'none'); + $(this.box).on('selectstart', function () { return false; }); + } + // regular record select + if ($(event.target).parents().hasClass('w2ui-head') || $(event.target).hasClass('w2ui-head')) return; + if (obj.last.move && obj.last.move.type == 'expand') return; + // if altKey - alow text selection + if (event.altKey) { + $(obj.box).off('selectstart'); + $(obj.box).find('.w2ui-grid-body') + .css('user-select', 'text') + .css('-webkit-user-select', 'text') + .css('-moz-user-select', 'text') + .css('-ms-user-select', 'text'); + obj.selectNone(); + obj.last.move = { type: 'text-select' }; + obj.last.userSelect = 'text'; + } else { + if (!obj.multiSelect) return; + obj.last.move = { + x : event.screenX, + y : event.screenY, + divX : 0, + divY : 0, + recid : $(event.target).parents('tr').attr('recid'), + column : (event.target.tagName == 'TD' ? $(event.target).attr('col') : $(event.target).parents('td').attr('col')), + type : 'select', + ghost : false, + start : true + }; + } + $(document).on('mousemove', mouseMove); + $(document).on('mouseup', mouseStop); + } + + function mouseMove (event) { + var mv = obj.last.move; + if (!mv || mv.type != 'select') return; + mv.divX = (event.screenX - mv.x); + mv.divY = (event.screenY - mv.y); + if (Math.abs(mv.divX) <= 1 && Math.abs(mv.divY) <= 1) return; // only if moved more then 1px + obj.last.cancelClick = true; + if (obj.reorderRows == true) { + if (!mv.ghost) { + var row = $('#grid_'+ obj.name + '_rec_'+ mv.recid); + var tmp = row.parents('table').find('tr:first-child').clone(); + mv.offsetY = event.offsetY; + mv.from = mv.recid; + mv.pos = row.position(); + mv.ghost = $(row).clone(true); + mv.ghost.removeAttr('id'); + row.find('td:first-child').replaceWith('<td colspan="1000" style="height: '+ obj.recordHeight +'px; background-color: #ddd"></td>'); + var recs = $(obj.box).find('.w2ui-grid-records'); + recs.append('<table id="grid_'+ obj.name + '_ghost" style="position: absolute; z-index: 999999; opacity: 0.8; border-bottom: 2px dashed #aaa; border-top: 2px dashed #aaa; pointer-events: none;"></table>'); + $('#grid_'+ obj.name + '_ghost').append(tmp).append(mv.ghost); + } + var recid = $(event.target).parents('tr').attr('recid'); + if (recid != mv.from) { + var row1 = $('#grid_'+ obj.name + '_rec_'+ mv.recid); + var row2 = $('#grid_'+ obj.name + '_rec_'+ recid); + if (event.screenY - mv.lastY < 0) row1.after(row2); else row2.after(row1); + mv.lastY = event.screenY; + mv.to = recid; + } + var ghost = $('#grid_'+ obj.name + '_ghost'); + var recs = $(obj.box).find('.w2ui-grid-records'); + ghost.css({ + top : mv.pos.top + mv.divY + recs.scrollTop(), // + mv.offsetY - obj.recordHeight / 2, + left : mv.pos.left + }); + return; + } + if (mv.start && mv.recid) { + obj.selectNone(); + mv.start = false; + } + var newSel= []; + var recid = (event.target.tagName == 'TR' ? $(event.target).attr('recid') : $(event.target).parents('tr').attr('recid')); + if (typeof recid == 'undefined') return; + var ind1 = obj.get(mv.recid, true); + // |:wolfmanx:| this happens when selection is started on summary row + if (ind1 === null) return; + var ind2 = obj.get(recid, true); + // this happens when selection is extended into summary row (a good place to implement scrolling) + if (ind2 === null) return; + var col1 = parseInt(mv.column); + var col2 = parseInt(event.target.tagName == 'TD' ? $(event.target).attr('col') : $(event.target).parents('td').attr('col')); + if (ind1 > ind2) { var tmp = ind1; ind1 = ind2; ind2 = tmp; } + // check if need to refresh + var tmp = 'ind1:'+ ind1 +',ind2;'+ ind2 +',col1:'+ col1 +',col2:'+ col2; + if (mv.range == tmp) return; + mv.range = tmp; + for (var i = ind1; i <= ind2; i++) { + if (obj.last.searchIds.length > 0 && obj.last.searchIds.indexOf(i) == -1) continue; + if (obj.selectType != 'row') { + if (col1 > col2) { var tmp = col1; col1 = col2; col2 = tmp; } + var tmp = []; + for (var c = col1; c <= col2; c++) { + if (obj.columns[c].hidden) continue; + newSel.push({ recid: obj.records[i].recid, column: parseInt(c) }); + } + } else { + newSel.push(obj.records[i].recid); + } + } + if (obj.selectType != 'row') { + var sel = obj.getSelection(); + // add more items + var tmp = []; + for (var ns in newSel) { + var flag = false; + for (var s in sel) if (newSel[ns].recid == sel[s].recid && newSel[ns].column == sel[s].column) flag = true; + if (!flag) tmp.push({ recid: newSel[ns].recid, column: newSel[ns].column }); + } + obj.select.apply(obj, tmp); + // remove items + var tmp = []; + for (var s in sel) { + var flag = false; + for (var ns in newSel) if (newSel[ns].recid == sel[s].recid && newSel[ns].column == sel[s].column) flag = true; + if (!flag) tmp.push({ recid: sel[s].recid, column: sel[s].column }); + } + obj.unselect.apply(obj, tmp); + } else { + if (obj.multiSelect) { + var sel = obj.getSelection(); + for (var ns in newSel) if (sel.indexOf(newSel[ns]) == -1) obj.select(newSel[ns]); // add more items + for (var s in sel) if (newSel.indexOf(sel[s]) == -1) obj.unselect(sel[s]); // remove items + } + } + } + + function mouseStop (event) { + var mv = obj.last.move; + setTimeout(function () { delete obj.last.cancelClick; }, 1); + if ($(event.target).parents().hasClass('.w2ui-head') || $(event.target).hasClass('.w2ui-head')) return; + if (mv && mv.type == 'select') { + if (obj.reorderRows == true) { + var ind1 = obj.get(mv.from, true); + var tmp = obj.records[ind1]; + obj.records.splice(ind1, 1); + var ind2 = obj.get(mv.to, true); + if (ind1 > ind2) obj.records.splice(ind2, 0, tmp); else obj.records.splice(ind2+1, 0, tmp); + $('#grid_'+ obj.name + '_ghost').remove(); + obj.refresh(); + } + } + delete obj.last.move; + $(document).off('mousemove', mouseMove); + $(document).off('mouseup', mouseStop); + } + }, + + destroy: function () { + // event before + var eventData = this.trigger({ phase: 'before', target: this.name, type: 'destroy' }); + if (eventData.isCancelled === true) return; + // remove events + $(window).off('resize', this.tmp_resize); + // clean up + if (typeof this.toolbar == 'object' && this.toolbar.destroy) this.toolbar.destroy(); + if ($(this.box).find('#grid_'+ this.name +'_body').length > 0) { + $(this.box) + .removeAttr('name') + .removeClass('w2ui-reset w2ui-grid') + .html(''); + } + delete w2ui[this.name]; + // event after + this.trigger($.extend(eventData, { phase: 'after' })); + }, + + // =========================================== + // --- Internal Functions + + initColumnOnOff: function () { + if (!this.show.toolbarColumns) return; + var obj = this; + var col_html = '<div class="w2ui-col-on-off">'+ + '<table><tr>'+ + '<td style="width: 30px">'+ + ' <input id="grid_'+ this.name +'_column_ln_check" type="checkbox" tabIndex="-1" '+ (obj.show.lineNumbers ? 'checked' : '') + + ' onclick="w2ui[\''+ obj.name +'\'].columnOnOff(this, event, \'line-numbers\');">'+ + '</td>'+ + '<td onclick="w2ui[\''+ obj.name +'\'].columnOnOff(this, event, \'line-numbers\'); $(\'#w2ui-overlay\')[0].hide();">'+ + ' <label for="grid_'+ this.name +'_column_ln_check">'+ w2utils.lang('Line #') +'</label>'+ + '</td></tr>'; + for (var c in this.columns) { + var col = this.columns[c]; + var tmp = this.columns[c].caption; + if (col.hideable === false) continue; + if (!tmp && this.columns[c].hint) tmp = this.columns[c].hint; + if (!tmp) tmp = '- column '+ (parseInt(c) + 1) +' -'; + col_html += '<tr>'+ + '<td style="width: 30px">'+ + ' <input id="grid_'+ this.name +'_column_'+ c +'_check" type="checkbox" tabIndex="-1" '+ (col.hidden ? '' : 'checked') + + ' onclick="w2ui[\''+ obj.name +'\'].columnOnOff(this, event, \''+ col.field +'\');">'+ + '</td>'+ + '<td>'+ + ' <label for="grid_'+ this.name +'_column_'+ c +'_check">'+ tmp + '</label>'+ + '</td>'+ + '</tr>'; + } + col_html += '<tr><td colspan="2"><div style="border-top: 1px solid #ddd;"></div></td></tr>'; + var url = (typeof this.url != 'object' ? this.url : this.url.get); + if (url && obj.show.skipRecords) { + col_html += + '<tr><td colspan="2" style="padding: 0px">'+ + ' <div style="cursor: pointer; padding: 2px 8px; cursor: default">'+ w2utils.lang('Skip') + + ' <input type="text" style="width: 45px" value="'+ this.offset +'" '+ + ' onkeypress="if (event.keyCode == 13) { '+ + ' w2ui[\''+ obj.name +'\'].skip(this.value); '+ + ' $(\'#w2ui-overlay\')[0].hide(); '+ + ' }"> '+ w2utils.lang('Records')+ + ' </div>'+ + '</td></tr>'; + } + col_html += '<tr><td colspan="2" onclick="w2ui[\''+ obj.name +'\'].stateSave(); $(\'#w2ui-overlay\')[0].hide();">'+ + ' <div style="cursor: pointer; padding: 4px 8px; cursor: default">'+ w2utils.lang('Save Grid State') + '</div>'+ + '</td></tr>'+ + '<tr><td colspan="2" onclick="w2ui[\''+ obj.name +'\'].stateReset(); $(\'#w2ui-overlay\')[0].hide();">'+ + ' <div style="cursor: pointer; padding: 4px 8px; cursor: default">'+ w2utils.lang('Restore Default State') + '</div>'+ + '</td></tr>'; + col_html += "</table></div>"; + this.toolbar.get('w2ui-column-on-off').html = col_html; + }, + + /** + * + * @param box, grid object + * @returns {{remove: Function}} contains a closure around all events to ensure they are removed from the dom + */ + initColumnDrag: function ( box ) { + //throw error if using column groups + if ( this.columnGroups && this.columnGroups.length ) throw 'Draggable columns are not currently supported with column groups.'; + + var obj = this, + _dragData = {}; + _dragData.lastInt = null; + _dragData.pressed = false; + _dragData.timeout = null;_dragData.columnHead = null; + + //attach orginal event listener + $(obj.box).on('mousedown', dragColStart); + $(obj.box).on('mouseup', catchMouseup); + + function catchMouseup(){ + _dragData.pressed = false; + clearTimeout( _dragData.timeout ); + } + /** + * + * @param event, mousedown + * @returns {boolean} false, preventsDefault + */ + function dragColStart ( event ) { + if ( _dragData.timeout ) clearTimeout( _dragData.timeout ); + var self = this; + _dragData.pressed = true; + + _dragData.timeout = setTimeout(function(){ + if ( !_dragData.pressed ) return; + + var eventData, + columns, + selectedCol, + origColumn, + origColumnNumber, + invalidPreColumns = [ 'w2ui-col-number', 'w2ui-col-expand', 'w2ui-col-select' ], + invalidPostColumns = [ 'w2ui-head-last' ], + invalidColumns = invalidPreColumns.concat( invalidPostColumns ), + preColumnsSelector = '.w2ui-col-number, .w2ui-col-expand, .w2ui-col-select', + preColHeadersSelector = '.w2ui-head.w2ui-col-number, .w2ui-head.w2ui-col-expand, .w2ui-head.w2ui-col-select'; + + // do nothing if it is not a header + if ( !$( event.originalEvent.target ).parents().hasClass( 'w2ui-head' ) ) return; + + // do nothing if it is an invalid column + for ( var i = 0, l = invalidColumns.length; i < l; i++ ){ + if ( $( event.originalEvent.target ).parents().hasClass( invalidColumns[ i ] ) ) return; + } + + _dragData.numberPreColumnsPresent = $( obj.box ).find( preColHeadersSelector ).length; + + //start event for drag start + _dragData.columnHead = origColumn = $( event.originalEvent.target ).parents( '.w2ui-head' ); + origColumnNumber = parseInt( origColumn.attr( 'col' ), 10); + eventData = obj.trigger({ type: 'columnDragStart', phase: 'before', originalEvent: event, origColumnNumber: origColumnNumber, target: origColumn[0] }); + if ( eventData.isCancelled === true ) return false; + + columns = _dragData.columns = $( obj.box ).find( '.w2ui-head:not(.w2ui-head-last)' ); + + //add events + $( document ).on( 'mouseup', dragColEnd ); + $( document ).on( 'mousemove', dragColOver ); + + _dragData.originalPos = parseInt( $( event.originalEvent.target ).parent( '.w2ui-head' ).attr( 'col' ), 10 ); + //_dragData.columns.css({ overflow: 'visible' }).children( 'div' ).css({ overflow: 'visible' }); + + //configure and style ghost image + _dragData.ghost = $( self ).clone( true ); + + //hide other elements on ghost except the grid body + $( _dragData.ghost ).find( '[col]:not([col="' + _dragData.originalPos + '"]), .w2ui-toolbar, .w2ui-grid-header' ).remove(); + $( _dragData.ghost ).find( preColumnsSelector ).remove(); + $( _dragData.ghost ).find( '.w2ui-grid-body' ).css({ top: 0 }); + + selectedCol = $( _dragData.ghost ).find( '[col="' + _dragData.originalPos + '"]' ); + $( document.body ).append( _dragData.ghost ); + + $( _dragData.ghost ).css({ + width: 0, + height: 0, + margin: 0, + position: 'fixed', + zIndex: 999999, + opacity: 0 + }).addClass( '.w2ui-grid-ghost' ).animate({ + width: selectedCol.width(), + height: $(obj.box).find('.w2ui-grid-body:first').height(), + left : event.pageX, + top : event.pageY, + opacity: .8 + }, 0 ); + + //establish current offsets + _dragData.offsets = []; + for ( var i = 0, l = columns.length; i < l; i++ ) { + _dragData.offsets.push( $( columns[ i ] ).offset().left ); + } + + //conclude event + obj.trigger( $.extend( eventData, { phase: 'after' } ) ); + }, 150 );//end timeout wrapper + } + + function dragColOver ( event ) { + if ( !_dragData.pressed ) return; + + var cursorX = event.originalEvent.pageX, + cursorY = event.originalEvent.pageY, + offsets = _dragData.offsets, + lastWidth = $( '.w2ui-head:not(.w2ui-head-last)' ).width(); + + _dragData.targetInt = Math.max(_dragData.numberPreColumnsPresent,targetIntersection( cursorX, offsets, lastWidth )); + + markIntersection( _dragData.targetInt ); + trackGhost( cursorX, cursorY ); + } + + function dragColEnd ( event ) { + _dragData.pressed = false; + + var eventData, + target, + selected, + columnConfig, + targetColumn, + ghosts = $( '.w2ui-grid-ghost' ); + + //start event for drag start + eventData = obj.trigger({ type: 'columnDragEnd', phase: 'before', originalEvent: event, target: _dragData.columnHead[0] }); + if ( eventData.isCancelled === true ) return false; + + selected = obj.columns[ _dragData.originalPos ]; + columnConfig = obj.columns; + targetColumn = $( _dragData.columns[ Math.min(_dragData.lastInt, _dragData.columns.length - 1) ] ); + target = (_dragData.lastInt < _dragData.columns.length) ? parseInt(targetColumn.attr('col')) : columnConfig.length; + + if ( target !== _dragData.originalPos + 1 && target !== _dragData.originalPos && targetColumn && targetColumn.length ) { + $( _dragData.ghost ).animate({ + top: $( obj.box ).offset().top, + left: targetColumn.offset().left, + width: 0, + height: 0, + opacity:.2 + }, 300, function(){ + $( this ).remove(); + ghosts.remove(); + }); + + columnConfig.splice( target, 0, $.extend( {}, selected ) ); + columnConfig.splice( columnConfig.indexOf( selected ), 1); + + } else { + $( _dragData.ghost ).remove(); + ghosts.remove(); + } + + //_dragData.columns.css({ overflow: '' }).children( 'div' ).css({ overflow: '' }); + + $( document ).off( 'mouseup', dragColEnd ); + $( document ).off( 'mousemove', dragColOver ); + if ( _dragData.marker ) _dragData.marker.remove(); + _dragData = {}; + + obj.refresh(); + + //conclude event + obj.trigger( $.extend( eventData, { phase: 'after', targetColumnNumber: target - 1 } ) ); + } + + function markIntersection( intersection ){ + if ( !_dragData.marker && !_dragData.markerLeft ) { + _dragData.marker = $('<div class="col-intersection-marker">' + + '<div class="top-marker"></div>' + + '<div class="bottom-marker"></div>' + + '</div>'); + _dragData.markerLeft = $('<div class="col-intersection-marker">' + + '<div class="top-marker"></div>' + + '<div class="bottom-marker"></div>' + + '</div>'); + } + + if ( !_dragData.lastInt || _dragData.lastInt !== intersection ){ + _dragData.lastInt = intersection; + _dragData.marker.remove(); + _dragData.markerLeft.remove(); + $('.w2ui-head').removeClass('w2ui-col-intersection'); + + //if the current intersection is greater than the number of columns add the marker to the end of the last column only + if ( intersection >= _dragData.columns.length ) { + $( _dragData.columns[ _dragData.columns.length - 1 ] ).children( 'div:last' ).append( _dragData.marker.addClass( 'right' ).removeClass( 'left' ) ); + $( _dragData.columns[ _dragData.columns.length - 1 ] ).addClass('w2ui-col-intersection'); + } else if ( intersection <= _dragData.numberPreColumnsPresent ) { + //if the current intersection is on the column numbers place marker on first available column only + $( _dragData.columns[ _dragData.numberPreColumnsPresent ] ).prepend( _dragData.marker.addClass( 'left' ).removeClass( 'right' ) ).css({ position: 'relative' }); + $( _dragData.columns[ _dragData.numberPreColumnsPresent ] ).prev().addClass('w2ui-col-intersection'); + } else { + //otherwise prepend the marker to the targeted column and append it to the previous column + $( _dragData.columns[intersection] ).children( 'div:last' ).prepend( _dragData.marker.addClass( 'left' ).removeClass( 'right' ) ); + $( _dragData.columns[intersection] ).prev().children( 'div:last' ).append( _dragData.markerLeft.addClass( 'right' ).removeClass( 'left' ) ).css({ position: 'relative' }); + $( _dragData.columns[intersection - 1] ).addClass('w2ui-col-intersection'); + } + } + } + + function targetIntersection( cursorX, offsets, lastWidth ){ + if ( cursorX <= offsets[0] ) { + return 0; + } else if ( cursorX >= offsets[offsets.length - 1] + lastWidth ) { + return offsets.length; + } else { + for ( var i = 0, l = offsets.length; i < l; i++ ) { + var thisOffset = offsets[ i ]; + var nextOffset = offsets[ i + 1 ] || offsets[ i ] + lastWidth; + var midpoint = ( nextOffset - offsets[ i ]) / 2 + offsets[ i ]; + + if ( cursorX > thisOffset && cursorX <= midpoint ) { + return i; + } else if ( cursorX > midpoint && cursorX <= nextOffset ) { + return i + 1; + } + } + return intersection; + } + } + + function trackGhost( cursorX, cursorY ){ + $( _dragData.ghost ).css({ + left: cursorX - 10, + top: cursorY - 10 + }); + } + + //return an object to remove drag if it has ever been enabled + return { + remove: function(){ + $( obj.box ).off( 'mousedown', dragColStart ); + $( obj.box ).off( 'mouseup', catchMouseup ); + $( obj.box ).find( '.w2ui-head' ).removeAttr( 'draggable' ); + obj.last.columnDrag = false; + } + } + }, + + columnOnOff: function (el, event, field) { + // event before + var eventData = this.trigger({ phase: 'before', target: this.name, type: 'columnOnOff', checkbox: el, field: field, originalEvent: event }); + if (eventData.isCancelled === true) return; + // regular processing + var obj = this; + // collapse expanded rows + for (var r in this.records) { + if (this.records[r].expanded === true) this.records[r].expanded = false + } + // show/hide + var hide = true; + if (field == 'line-numbers') { + this.show.lineNumbers = !this.show.lineNumbers; + this.refresh(); + } else { + var col = this.getColumn(field); + if (col.hidden) { + $(el).prop('checked', true); + this.showColumn(col.field); + } else { + $(el).prop('checked', false); + this.hideColumn(col.field); + } + hide = false; + } + if (hide) { + setTimeout(function () { + $().w2overlay('', { name: 'searches-'+ this.name }); + obj.toolbar.uncheck('column-on-off'); + }, 100); + } + // event after + this.trigger($.extend(eventData, { phase: 'after' })); + }, + + initToolbar: function () { + // -- if toolbar is true + if (typeof this.toolbar['render'] == 'undefined') { + var tmp_items = this.toolbar.items; + this.toolbar.items = []; + this.toolbar = $().w2toolbar($.extend(true, {}, this.toolbar, { name: this.name +'_toolbar', owner: this })); + + // ============================================= + // ------ Toolbar Generic buttons + + if (this.show.toolbarReload) { + this.toolbar.items.push($.extend(true, {}, this.buttons['reload'])); + } + if (this.show.toolbarColumns) { + this.toolbar.items.push($.extend(true, {}, this.buttons['columns'])); + } + if (this.show.toolbarReload || this.show.toolbarColumn) { + this.toolbar.items.push({ type: 'break', id: 'w2ui-break0' }); + } + if (this.show.toolbarSearch) { + var html = + '<div class="w2ui-toolbar-search">'+ + '<table cellpadding="0" cellspacing="0"><tr>'+ + ' <td>'+ this.buttons['search'].html +'</td>'+ + ' <td>'+ + ' <input id="grid_'+ this.name +'_search_all" class="w2ui-search-all" '+ + ' placeholder="'+ this.last.caption +'" value="'+ this.last.search +'"'+ + ' onkeydown="if (event.keyCode == 13 && w2utils.isIE) this.onchange();"'+ + ' onchange="'+ + ' var val = this.value; '+ + ' var fld = $(this).data(\'w2field\'); '+ + ' if (fld) val = fld.clean(val);'+ + ' w2ui[\''+ this.name +'\'].search(w2ui[\''+ this.name +'\'].last.field, val); '+ + ' ">'+ + ' </td>'+ + ' <td>'+ + ' <div title="'+ w2utils.lang('Clear Search') +'" class="w2ui-search-clear" id="grid_'+ this.name +'_searchClear" '+ + ' onclick="var obj = w2ui[\''+ this.name +'\']; obj.searchReset();" '+ + ' > </div>'+ + ' </td>'+ + '</tr></table>'+ + '</div>'; + this.toolbar.items.push({ type: 'html', id: 'w2ui-search', html: html }); + if (this.multiSearch && this.searches.length > 0) { + this.toolbar.items.push($.extend(true, {}, this.buttons['search-go'])); + } + } + if (this.show.toolbarSearch && (this.show.toolbarAdd || this.show.toolbarEdit || this.show.toolbarDelete || this.show.toolbarSave)) { + this.toolbar.items.push({ type: 'break', id: 'w2ui-break1' }); + } + if (this.show.toolbarAdd) { + this.toolbar.items.push($.extend(true, {}, this.buttons['add'])); + } + if (this.show.toolbarEdit) { + this.toolbar.items.push($.extend(true, {}, this.buttons['edit'])); + } + if (this.show.toolbarDelete) { + this.toolbar.items.push($.extend(true, {}, this.buttons['delete'])); + } + if (this.show.toolbarSave) { + if (this.show.toolbarAdd || this.show.toolbarDelete || this.show.toolbarEdit) { + this.toolbar.items.push({ type: 'break', id: 'w2ui-break2' }); + } + this.toolbar.items.push($.extend(true, {}, this.buttons['save'])); + } + // add original buttons + for (var i in tmp_items) this.toolbar.items.push(tmp_items[i]); + + // ============================================= + // ------ Toolbar onClick processing + + var obj = this; + this.toolbar.on('click', function (event) { + var eventData = obj.trigger({ phase: 'before', type: 'toolbar', target: event.target, originalEvent: event }); + if (eventData.isCancelled === true) return; + var id = event.target; + switch (id) { + case 'w2ui-reload': + var eventData2 = obj.trigger({ phase: 'before', type: 'reload', target: obj.name }); + if (eventData2.isCancelled === true) return false; + obj.reload(); + obj.trigger($.extend(eventData2, { phase: 'after' })); + break; + case 'w2ui-column-on-off': + obj.initColumnOnOff(); + obj.initResize(); + obj.resize(); + break; + case 'w2ui-search-advanced': + var tb = this; + var it = this.get(id); + if (it.checked) { + obj.searchClose(); + setTimeout(function () { tb.uncheck(id); }, 1); + } else { + obj.searchOpen(); + event.originalEvent.stopPropagation(); + function tmp_close() { + if ($('#w2ui-overlay-searches-'+ obj.name).data('keepOpen') === true) return; + tb.uncheck(id); + $(document).off('click', 'body', tmp_close); + } + $(document).on('click', 'body', tmp_close); + } + break; + case 'w2ui-add': + // events + var eventData = obj.trigger({ phase: 'before', target: obj.name, type: 'add', recid: null }); + obj.trigger($.extend(eventData, { phase: 'after' })); + break; + case 'w2ui-edit': + var sel = obj.getSelection(); + var recid = null; + if (sel.length == 1) recid = sel[0]; + // events + var eventData = obj.trigger({ phase: 'before', target: obj.name, type: 'edit', recid: recid }); + obj.trigger($.extend(eventData, { phase: 'after' })); + break; + case 'w2ui-delete': + obj["delete"](); + break; + case 'w2ui-save': + obj.save(); + break; + } + // no default action + obj.trigger($.extend(eventData, { phase: 'after' })); + }); + } + return; + }, + + initResize: function () { + var obj = this; + //if (obj.resizing === true) return; + $(this.box).find('.w2ui-resizer') + .off('click') + .on('click', function (event) { + if (event.stopPropagation) event.stopPropagation(); else event.cancelBubble = true; + if (event.preventDefault) event.preventDefault(); + }) + .off('mousedown') + .on('mousedown', function (event) { + if (!event) event = window.event; + if (!window.addEventListener) { window.document.attachEvent('onselectstart', function() { return false; } ); } + obj.resizing = true; + obj.last.tmp = { + x : event.screenX, + y : event.screenY, + gx : event.screenX, + gy : event.screenY, + col : parseInt($(this).attr('name')) + }; + if (event.stopPropagation) event.stopPropagation(); else event.cancelBubble = true; + if (event.preventDefault) event.preventDefault(); + // fix sizes + for (var c in obj.columns) { + if (typeof obj.columns[c].sizeOriginal == 'undefined') obj.columns[c].sizeOriginal = obj.columns[c].size; + obj.columns[c].size = obj.columns[c].sizeCalculated; + } + var eventData = { phase: 'before', type: 'columnResize', target: obj.name, column: obj.last.tmp.col, field: obj.columns[obj.last.tmp.col].field }; + eventData = obj.trigger($.extend(eventData, { resizeBy: 0, originalEvent: event })); + // set move event + var mouseMove = function (event) { + if (obj.resizing != true) return; + if (!event) event = window.event; + // event before + eventData = obj.trigger($.extend(eventData, { resizeBy: (event.screenX - obj.last.tmp.gx), originalEvent: event })); + if (eventData.isCancelled === true) { eventData.isCancelled = false; return; } + // default action + obj.last.tmp.x = (event.screenX - obj.last.tmp.x); + obj.last.tmp.y = (event.screenY - obj.last.tmp.y); + obj.columns[obj.last.tmp.col].size = (parseInt(obj.columns[obj.last.tmp.col].size) + obj.last.tmp.x) + 'px'; + obj.resizeRecords(); + // reset + obj.last.tmp.x = event.screenX; + obj.last.tmp.y = event.screenY; + } + var mouseUp = function (event) { + delete obj.resizing; + $(document).off('mousemove', 'body'); + $(document).off('mouseup', 'body'); + obj.resizeRecords(); + // event before + obj.trigger($.extend(eventData, { phase: 'after', originalEvent: event })); + } + $(document).on('mousemove', 'body', mouseMove); + $(document).on('mouseup', 'body', mouseUp); + }) + .each(function (index, el) { + var td = $(el).parent(); + $(el).css({ + "height" : '25px', + "margin-left" : (td.width() - 3) + 'px' + }) + }); + }, + + resizeBoxes: function () { + // elements + var main = $(this.box).find('> div'); + var header = $('#grid_'+ this.name +'_header'); + var toolbar = $('#grid_'+ this.name +'_toolbar'); + var summary = $('#grid_'+ this.name +'_summary'); + var footer = $('#grid_'+ this.name +'_footer'); + var body = $('#grid_'+ this.name +'_body'); + var columns = $('#grid_'+ this.name +'_columns'); + var records = $('#grid_'+ this.name +'_records'); + + if (this.show.header) { + header.css({ + top: '0px', + left: '0px', + right: '0px' + }); + } + + if (this.show.toolbar) { + toolbar.css({ + top: ( 0 + (this.show.header ? w2utils.getSize(header, 'height') : 0) ) + 'px', + left: '0px', + right: '0px' + }); + } + if (this.show.footer) { + footer.css({ + bottom: '0px', + left: '0px', + right: '0px' + }); + } + if (this.summary.length > 0) { + summary.css({ + bottom: ( 0 + (this.show.footer ? w2utils.getSize(footer, 'height') : 0) ) + 'px', + left: '0px', + right: '0px' + }); + } + body.css({ + top: ( 0 + (this.show.header ? w2utils.getSize(header, 'height') : 0) + (this.show.toolbar ? w2utils.getSize(toolbar, 'height') : 0) ) + 'px', + bottom: ( 0 + (this.show.footer ? w2utils.getSize(footer, 'height') : 0) + (this.summary.length > 0 ? w2utils.getSize(summary, 'height') : 0) ) + 'px', + left: '0px', + right: '0px' + }); + }, + + resizeRecords: function () { + var obj = this; + // remove empty records + $(this.box).find('.w2ui-empty-record').remove(); + // -- Calculate Column size in PX + var box = $(this.box); + var grid = $(this.box).find('> div'); + var header = $('#grid_'+ this.name +'_header'); + var toolbar = $('#grid_'+ this.name +'_toolbar'); + var summary = $('#grid_'+ this.name +'_summary'); + var footer = $('#grid_'+ this.name +'_footer'); + var body = $('#grid_'+ this.name +'_body'); + var columns = $('#grid_'+ this.name +'_columns'); + var records = $('#grid_'+ this.name +'_records'); + + // body might be expanded by data + if (!this.fixedBody) { + // allow it to render records, then resize + var calculatedHeight = w2utils.getSize(columns, 'height') + + w2utils.getSize($('#grid_'+ obj.name +'_records table'), 'height'); + obj.height = calculatedHeight + + w2utils.getSize(grid, '+height') + + (obj.show.header ? w2utils.getSize(header, 'height') : 0) + + (obj.show.toolbar ? w2utils.getSize(toolbar, 'height') : 0) + + (summary.css('display') != 'none' ? w2utils.getSize(summary, 'height') : 0) + + (obj.show.footer ? w2utils.getSize(footer, 'height') : 0); + grid.css('height', obj.height); + body.css('height', calculatedHeight); + box.css('height', w2utils.getSize(grid, 'height') + w2utils.getSize(box, '+height')); + } else { + // fixed body height + var calculatedHeight = grid.height() + - (this.show.header ? w2utils.getSize(header, 'height') : 0) + - (this.show.toolbar ? w2utils.getSize(toolbar, 'height') : 0) + - (summary.css('display') != 'none' ? w2utils.getSize(summary, 'height') : 0) + - (this.show.footer ? w2utils.getSize(footer, 'height') : 0); + body.css('height', calculatedHeight); + } + + var buffered = this.records.length; + if (this.searchData.length != 0 && !this.url) buffered = this.last.searchIds.length; + // check overflow + var bodyOverflowX = false; + var bodyOverflowY = false; + if (body.width() < $(records).find('>table').width()) bodyOverflowX = true; + if (body.height() - columns.height() < $(records).find('>table').height() + (bodyOverflowX ? w2utils.scrollBarSize() : 0)) bodyOverflowY = true; + if (!this.fixedBody) { bodyOverflowY = false; bodyOverflowX = false; } + if (bodyOverflowX || bodyOverflowY) { + columns.find('> table > tbody > tr:nth-child(1) td.w2ui-head-last').css('width', w2utils.scrollBarSize()).show(); + records.css({ + top: ((this.columnGroups.length > 0 && this.show.columns ? 1 : 0) + w2utils.getSize(columns, 'height')) +'px', + "-webkit-overflow-scrolling": "touch", + "overflow-x": (bodyOverflowX ? 'auto' : 'hidden'), + "overflow-y": (bodyOverflowY ? 'auto' : 'hidden') }); + } else { + columns.find('> table > tbody > tr:nth-child(1) td.w2ui-head-last').hide(); + records.css({ + top: ((this.columnGroups.length > 0 && this.show.columns ? 1 : 0) + w2utils.getSize(columns, 'height')) +'px', + overflow: 'hidden' + }); + if (records.length > 0) { this.last.scrollTop = 0; this.last.scrollLeft = 0; } // if no scrollbars, always show top + } + if (this.show.emptyRecords && !bodyOverflowY) { + var max = Math.floor(records.height() / this.recordHeight) + 1; + if (this.fixedBody) { + for (var di = buffered; di <= max; di++) { + var html = ''; + html += '<tr class="'+ (di % 2 ? 'w2ui-even' : 'w2ui-odd') + ' w2ui-empty-record" style="height: '+ this.recordHeight +'px">'; + if (this.show.lineNumbers) html += '<td class="w2ui-col-number"></td>'; + if (this.show.selectColumn) html += '<td class="w2ui-grid-data w2ui-col-select"></td>'; + if (this.show.expandColumn) html += '<td class="w2ui-grid-data w2ui-col-expand"></td>'; + var j = 0; + while (true && this.columns.length > 0) { + var col = this.columns[j]; + if (col.hidden) { j++; if (typeof this.columns[j] == 'undefined') break; else continue; } + html += '<td class="w2ui-grid-data" '+ (typeof col.attr != 'undefined' ? col.attr : '') +' col="'+ j +'"></td>'; + j++; + if (typeof this.columns[j] == 'undefined') break; + } + html += '<td class="w2ui-grid-data-last"></td>'; + html += '</tr>'; + $('#grid_'+ this.name +'_records > table').append(html); + } + } + } + if (body.length > 0) { + var width_max = parseInt(body.width()) + - (bodyOverflowY ? w2utils.scrollBarSize() : 0) + - (this.show.lineNumbers ? 34 : 0) + - (this.show.selectColumn ? 26 : 0) + - (this.show.expandColumn ? 26 : 0); + var width_box = width_max; + var percent = 0; + // gridMinWidth processiong + var restart = false; + for (var i = 0; i < this.columns.length; i++) { + var col = this.columns[i]; + if (col.gridMinWidth > 0) { + if (col.gridMinWidth > width_box && col.hidden !== true) { + col.hidden = true; + restart = true; + } + if (col.gridMinWidth < width_box && col.hidden === true) { + col.hidden = false; + restart = true; + } + } + } + if (restart === true) { + this.refresh(); + return; + } + // assign PX column s + for (var i = 0; i < this.columns.length; i++) { + var col = this.columns[i]; + if (col.hidden) continue; + if (String(col.size).substr(String(col.size).length-2).toLowerCase() == 'px') { + width_max -= parseFloat(col.size); + this.columns[i].sizeCalculated = col.size; + this.columns[i].sizeType = 'px'; + } else { + percent += parseFloat(col.size); + this.columns[i].sizeType = '%'; + delete col.sizeCorrected; + } + } + // if sum != 100% -- reassign proportionally + if (percent != 100 && percent > 0) { + for (var i = 0; i < this.columns.length; i++) { + var col = this.columns[i]; + if (col.hidden) continue; + if (col.sizeType == '%') { + col.sizeCorrected = Math.round(parseFloat(col.size) * 100 * 100 / percent) / 100 + '%'; + } + } + } + // calculate % columns + for (var i = 0; i < this.columns.length; i++) { + var col = this.columns[i]; + if (col.hidden) continue; + if (col.sizeType == '%') { + if (typeof this.columns[i].sizeCorrected != 'undefined') { + // make it 1px smaller, so margin of error can be calculated correctly + this.columns[i].sizeCalculated = Math.floor(width_max * parseFloat(col.sizeCorrected) / 100) - 1 + 'px'; + } else { + // make it 1px smaller, so margin of error can be calculated correctly + this.columns[i].sizeCalculated = Math.floor(width_max * parseFloat(col.size) / 100) - 1 + 'px'; + } + } + } + } + // fix margin of error that is due percentage calculations + var width_cols = 0; + for (var i = 0; i < this.columns.length; i++) { + var col = this.columns[i]; + if (col.hidden) continue; + if (typeof col.min == 'undefined') col.min = 20; + if (parseInt(col.sizeCalculated) < parseInt(col.min)) col.sizeCalculated = col.min + 'px'; + if (parseInt(col.sizeCalculated) > parseInt(col.max)) col.sizeCalculated = col.max + 'px'; + width_cols += parseInt(col.sizeCalculated); + } + var width_diff = parseInt(width_box) - parseInt(width_cols); + if (width_diff > 0 && percent > 0) { + var i = 0; + while (true) { + var col = this.columns[i]; + if (typeof col == 'undefined') { i = 0; continue; } + if (col.hidden || col.sizeType == 'px') { i++; continue; } + col.sizeCalculated = (parseInt(col.sizeCalculated) + 1) + 'px'; + width_diff--; + if (width_diff == 0) break; + i++; + } + } else if (width_diff > 0) { + columns.find('> table > tbody > tr:nth-child(1) td.w2ui-head-last').css('width', w2utils.scrollBarSize()).show(); + } + // resize columns + columns.find('> table > tbody > tr:nth-child(1) td').each(function (index, el) { + var ind = $(el).attr('col'); + if (typeof ind != 'undefined' && obj.columns[ind]) $(el).css('width', obj.columns[ind].sizeCalculated); + // last column + if ($(el).hasClass('w2ui-head-last')) { + $(el).css('width', w2utils.scrollBarSize() + (width_diff > 0 && percent == 0 ? width_diff : 0) + 'px'); + } + }); + // if there are column groups - hide first row (needed for sizing) + if (columns.find('> table > tbody > tr').length == 3) { + columns.find('> table > tbody > tr:nth-child(1) td').html('').css({ + 'height' : '0px', + 'border' : '0px', + 'padding': '0px', + 'margin' : '0px' + }); + } + // resize records + records.find('> table > tbody > tr:nth-child(1) td').each(function (index, el) { + var ind = $(el).attr('col'); + if (typeof ind != 'undefined' && obj.columns[ind]) $(el).css('width', obj.columns[ind].sizeCalculated); + // last column + if ($(el).hasClass('w2ui-grid-data-last')) { + $(el).css('width', (width_diff > 0 && percent == 0 ? width_diff : 0) + 'px'); + } + }); + // resize summary + summary.find('> table > tbody > tr:nth-child(1) td').each(function (index, el) { + var ind = $(el).attr('col'); + if (typeof ind != 'undefined' && obj.columns[ind]) $(el).css('width', obj.columns[ind].sizeCalculated); + // last column + if ($(el).hasClass('w2ui-grid-data-last')) { + $(el).css('width', w2utils.scrollBarSize() + (width_diff > 0 && percent == 0 ? width_diff : 0) + 'px'); + } + }); + this.initResize(); + this.refreshRanges(); + // apply last scroll if any + if (this.last.scrollTop != '' && records.length > 0) { + columns.prop('scrollLeft', this.last.scrollLeft); + records.prop('scrollTop', this.last.scrollTop); + records.prop('scrollLeft', this.last.scrollLeft); + } + }, + + getSearchesHTML: function () { + var html = '<table cellspacing="0">'; + var showBtn = false; + for (var i = 0; i < this.searches.length; i++) { + var s = this.searches[i]; + s.type = String(s.type).toLowerCase(); + if (s.hidden) continue; + var btn = ''; + if (showBtn == false) { + btn = '<button class="btn close-btn" onclick="obj = w2ui[\''+ this.name +'\']; if (obj) { obj.searchClose(); }">X</button'; + showBtn = true; + } + if (typeof s.inTag == 'undefined') s.inTag = ''; + if (typeof s.outTag == 'undefined') s.outTag = ''; + if (typeof s.type == 'undefined') s.type = 'text'; + if (['text', 'alphanumeric', 'combo'].indexOf(s.type) != -1) { + var operator = '<select id="grid_'+ this.name +'_operator_'+ i +'" onclick="event.stopPropagation();">'+ + ' <option value="is">'+ w2utils.lang('is') +'</option>'+ + ' <option value="begins">'+ w2utils.lang('begins') +'</option>'+ + ' <option value="contains">'+ w2utils.lang('contains') +'</option>'+ + ' <option value="ends">'+ w2utils.lang('ends') +'</option>'+ + '</select>'; + } + if (['int', 'float', 'money', 'currency', 'percent', 'date', 'time'].indexOf(s.type) != -1) { + var operator = '<select id="grid_'+ this.name +'_operator_'+ i +'" '+ + ' onchange="w2ui[\''+ this.name + '\'].initOperator(this, '+ i +');" onclick="event.stopPropagation();">'+ + ' <option value="is">'+ w2utils.lang('is') +'</option>'+ + (['int'].indexOf(s.type) != -1 ? '<option value="in">'+ w2utils.lang('in') +'</option>' : '') + + (['int'].indexOf(s.type) != -1 ? '<option value="not in">'+ w2utils.lang('not in') +'</option>' : '') + + '<option value="between">'+ w2utils.lang('between') +'</option>'+ + '</select>'; + } + if (['select', 'list', 'hex'].indexOf(s.type) != -1) { + var operator = '<select id="grid_'+ this.name +'_operator_'+ i +'" onclick="event.stopPropagation();">'+ + ' <option value="is">'+ w2utils.lang('is') +'</option>'+ + '</select>'; + } + if (['enum'].indexOf(s.type) != -1) { + var operator = '<select id="grid_'+ this.name +'_operator_'+ i +'" onclick="event.stopPropagation();">'+ + ' <option value="in">'+ w2utils.lang('in') +'</option>'+ + ' <option value="in">'+ w2utils.lang('not in') +'</option>'+ + '</select>'; + } + html += '<tr>'+ + ' <td class="close-btn">'+ btn +'</td>' + + ' <td class="caption">'+ s.caption +'</td>' + + ' <td class="operator">'+ operator +'</td>'+ + ' <td class="value">'; + + switch (s.type) { + case 'text': + case 'alphanumeric': + case 'hex': + case 'list': + case 'combo': + case 'enum': + html += '<input rel="search" type="text" style="width: 300px;" id="grid_'+ this.name +'_field_'+ i +'" name="'+ s.field +'" '+ s.inTag +'>'; + break; + + case 'int': + case 'float': + case 'money': + case 'currency': + case 'percent': + case 'date': + case 'time': + html += '<input rel="search" type="text" size="12" id="grid_'+ this.name +'_field_'+ i +'" name="'+ s.field +'" '+ s.inTag +'>'+ + '<span id="grid_'+ this.name +'_range_'+ i +'" style="display: none">'+ + ' - <input rel="search" type="text" style="width: 90px" id="grid_'+ this.name +'_field2_'+i+'" name="'+ s.field +'" '+ s.inTag +'>'+ + '</span>'; + break; + + case 'select': + html += '<select rel="search" id="grid_'+ this.name +'_field_'+ i +'" name="'+ s.field +'" '+ s.inTag +' onclick="event.stopPropagation();"></select>'; + break; + + } + html += s.outTag + + ' </td>' + + '</tr>'; + } + html += '<tr>'+ + ' <td colspan="4" class="actions">'+ + ' <div>'+ + ' <button class="btn" onclick="obj = w2ui[\''+ this.name +'\']; if (obj) { obj.searchReset(); }">'+ w2utils.lang('Reset') + '</button>'+ + ' <button class="btn btn-blue" onclick="obj = w2ui[\''+ this.name +'\']; if (obj) { obj.search(); }">'+ w2utils.lang('Search') + '</button>'+ + ' </div>'+ + ' </td>'+ + '</tr></table>'; + return html; + }, + + initOperator: function (el, search_ind) { + var obj = this; + var search = obj.searches[search_ind]; + var range = $('#grid_'+ obj.name + '_range_'+ search_ind); + var fld1 = $('#grid_'+ obj.name +'_field_'+ search_ind); + var fld2 = fld1.parent().find('span input'); + if ($(el).val() == 'in' || $(el).val() == 'not in') { fld1.w2field('clear'); } else { fld1.w2field(search.type); } + if ($(el).val() == 'between') { range.show(); fld2.w2field(search.type); } else { range.hide(); } + }, + + initSearches: function () { + var obj = this; + // init searches + for (var s in this.searches) { + var search = this.searches[s]; + var sdata = this.getSearchData(search.field); + search.type = String(search.type).toLowerCase(); + if (typeof search.options != 'object') search.options = {}; + // init types + switch (search.type) { + case 'text': + case 'alphanumeric': + $('#grid_'+ this.name +'_operator_'+s).val('begins'); + if (['alphanumeric', 'hex'].indexOf(search.type) != -1) { + $('#grid_'+ this.name +'_field_' + s).w2field(search.type, search.options); + } + break; + + case 'int': + case 'float': + case 'money': + case 'currency': + case 'percent': + case 'date': + case 'time': + if (sdata && sdata.type == 'int' && ['in', 'not in'].indexOf(sdata.operator) != -1) break; + $('#grid_'+ this.name +'_field_'+s).w2field(search.type, search.options); + $('#grid_'+ this.name +'_field2_'+s).w2field(search.type, search.options); + setTimeout(function () { // convert to date if it is number + $('#grid_'+ obj.name +'_field_'+s).keydown(); + $('#grid_'+ obj.name +'_field2_'+s).keydown(); + }, 1); + break; + + case 'hex': + break; + + case 'list': + case 'combo': + case 'enum': + var options = search.options; + if (search.type == 'list') options.selected = {}; + if (search.type == 'enum') options.selected = []; + if (sdata) options.selected = sdata.value; + $('#grid_'+ this.name +'_field_'+s).w2field(search.type, options); + if (search.type == 'combo') { + $('#grid_'+ this.name +'_operator_'+s).val('begins'); + } + break; + + case 'select': + // build options + var options = '<option value="">--</option>'; + for (var i in search.options.items) { + var si = search.options.items[i]; + if ($.isPlainObject(search.options.items[i])) { + var val = si.id; + var txt = si.text; + if (typeof val == 'undefined' && typeof si.value != 'undefined') val = si.value; + if (typeof txt == 'undefined' && typeof si.caption != 'undefined') txt = si.caption; + if (val == null) val = ''; + options += '<option value="'+ val +'">'+ txt +'</option>'; + } else { + options += '<option value="'+ si +'">'+ si +'</option>'; + } + } + $('#grid_'+ this.name +'_field_'+s).html(options); + break; + } + if (sdata != null) { + if (sdata.type == 'int' && ['in', 'not in'].indexOf(sdata.operator) != -1) { + $('#grid_'+ this.name +'_field_'+ s).w2field('clear').val(sdata.value); + } + $('#grid_'+ this.name +'_operator_'+ s).val(sdata.operator).trigger('change'); + if (!$.isArray(sdata.value)) { + if (typeof sdata.value != 'udefined') $('#grid_'+ this.name +'_field_'+ s).val(sdata.value).trigger('change'); + } else { + if (['in', 'not in'].indexOf(sdata.operator) != -1) { + $('#grid_'+ this.name +'_field_'+ s).val(sdata.value).trigger('change'); + } else { + $('#grid_'+ this.name +'_field_'+ s).val(sdata.value[0]).trigger('change'); + $('#grid_'+ this.name +'_field2_'+ s).val(sdata.value[1]).trigger('change'); + } + } + } + } + // add on change event + $('#w2ui-overlay-searches-'+ this.name +' .w2ui-grid-searches *[rel=search]').on('keypress', function (evnt) { + if (evnt.keyCode == 13) { + obj.search(); + $().w2overlay(); + } + }); + }, + + getColumnsHTML: function () { + var obj = this; + var html = ''; + if (this.show.columnHeaders) { + if (this.columnGroups.length > 0) { + html = getColumns(true) + getGroups() + getColumns(false); + } else { + html = getColumns(true); + } + } + return html; + + function getGroups () { + var html = '<tr>'; + // add empty group at the end + if (obj.columnGroups[obj.columnGroups.length-1].caption != '') obj.columnGroups.push({ caption: '' }); + + if (obj.show.lineNumbers) { + html += '<td class="w2ui-head w2ui-col-number">'+ + ' <div> </div>'+ + '</td>'; + } + if (obj.show.selectColumn) { + html += '<td class="w2ui-head w2ui-col-select">'+ + ' <div> </div>'+ + '</td>'; + } + if (obj.show.expandColumn) { + html += '<td class="w2ui-head w2ui-col-expand">'+ + ' <div> </div>'+ + '</td>'; + } + var ii = 0; + for (var i=0; i<obj.columnGroups.length; i++) { + var colg = obj.columnGroups[i]; + var col = obj.columns[ii]; + if (typeof colg.span == 'undefined' || colg.span != parseInt(colg.span)) colg.span = 1; + if (typeof colg.colspan != 'undefined') colg.span = colg.colspan; + if (colg.master === true) { + var sortStyle = ''; + for (var si in obj.sortData) { + if (obj.sortData[si].field == col.field) { + if (RegExp('asc', 'i').test(obj.sortData[si].direction)) sortStyle = 'w2ui-sort-up'; + if (RegExp('desc', 'i').test(obj.sortData[si].direction)) sortStyle = 'w2ui-sort-down'; + } + } + var resizer = ""; + if (col.resizable !== false) { + resizer = '<div class="w2ui-resizer" name="'+ ii +'"></div>'; + } + html += '<td class="w2ui-head '+ sortStyle +'" col="'+ ii + '" rowspan="2" colspan="'+ (colg.span + (i == obj.columnGroups.length-1 ? 1 : 0) ) +'" '+ + ' onclick="w2ui[\''+ obj.name +'\'].columnClick(\''+ col.field +'\', event);">'+ + resizer + + ' <div class="w2ui-col-group w2ui-col-header '+ (sortStyle ? 'w2ui-col-sorted' : '') +'">'+ + ' <div class="'+ sortStyle +'"></div>'+ + (!col.caption ? ' ' : col.caption) + + ' </div>'+ + '</td>'; + } else { + html += '<td class="w2ui-head" col="'+ ii + '" '+ + ' colspan="'+ (colg.span + (i == obj.columnGroups.length-1 ? 1 : 0) ) +'">'+ + ' <div class="w2ui-col-group">'+ + (!colg.caption ? ' ' : colg.caption) + + ' </div>'+ + '</td>'; + } + ii += colg.span; + } + html += '</tr>'; + return html; + } + + function getColumns (master) { + var html = '<tr>', + reorderCols = (obj.reorderColumns && (!obj.columnGroups || !obj.columnGroups.length)) ? ' w2ui-reorder-cols-head ' : ''; + if (obj.show.lineNumbers) { + html += '<td class="w2ui-head w2ui-col-number" onclick="w2ui[\''+ obj.name +'\'].columnClick(\'line-number\', event);">'+ + ' <div>#</div>'+ + '</td>'; + } + if (obj.show.selectColumn) { + html += '<td class="w2ui-head w2ui-col-select" '+ + ' onclick="if (event.stopPropagation) event.stopPropagation(); else event.cancelBubble = true;">'+ + ' <div>'+ + ' <input type="checkbox" id="grid_'+ obj.name +'_check_all" tabIndex="-1"'+ + ' style="' + (obj.multiSelect == false ? 'display: none;' : '') + '"'+ + ' onclick="if (this.checked) w2ui[\''+ obj.name +'\'].selectAll(); '+ + ' else w2ui[\''+ obj.name +'\'].selectNone(); '+ + ' if (event.stopPropagation) event.stopPropagation(); else event.cancelBubble = true;">'+ + ' </div>'+ + '</td>'; + } + if (obj.show.expandColumn) { + html += '<td class="w2ui-head w2ui-col-expand">'+ + ' <div> </div>'+ + '</td>'; + } + var ii = 0; + var id = 0; + for (var i=0; i<obj.columns.length; i++) { + var col = obj.columns[i]; + var colg = {}; + if (i == id) { + id = id + (typeof obj.columnGroups[ii] != 'undefined' ? parseInt(obj.columnGroups[ii].span) : 0); + ii++; + } + if (typeof obj.columnGroups[ii-1] != 'undefined') var colg = obj.columnGroups[ii-1]; + if (col.hidden) continue; + var sortStyle = ''; + for (var si in obj.sortData) { + if (obj.sortData[si].field == col.field) { + if (RegExp('asc', 'i').test(obj.sortData[si].direction)) sortStyle = 'w2ui-sort-up'; + if (RegExp('desc', 'i').test(obj.sortData[si].direction)) sortStyle = 'w2ui-sort-down'; + } + } + if (colg['master'] !== true || master) { // grouping of columns + var resizer = ""; + if (col.resizable !== false) { + resizer = '<div class="w2ui-resizer" name="'+ i +'"></div>'; + } + html += '<td col="'+ i +'" class="w2ui-head '+ sortStyle + reorderCols + '" ' + + ' onclick="w2ui[\''+ obj.name +'\'].columnClick(\''+ col.field +'\', event);">'+ + resizer + + ' <div class="w2ui-col-header '+ (sortStyle ? 'w2ui-col-sorted' : '') +'">'+ + ' <div class="'+ sortStyle +'"></div>'+ + (!col.caption ? ' ' : col.caption) + + ' </div>'+ + '</td>'; + } + } + html += '<td class="w2ui-head w2ui-head-last"><div> </div></td>'; + html += '</tr>'; + return html; + } + }, + + getRecordsHTML: function () { + var buffered = this.records.length; + if (this.searchData.length != 0 && !this.url) buffered = this.last.searchIds.length; + // larget number works better with chrome, smaller with FF. + if (buffered > 300) this.show_extra = 30; else this.show_extra = 300; + var records = $('#grid_'+ this.name +'_records'); + var limit = Math.floor(records.height() / this.recordHeight) + this.show_extra + 1; + if (!this.fixedBody || limit > buffered) limit = buffered; + // always need first record for resizing purposes + var html = '<table>' + this.getRecordHTML(-1, 0); + // first empty row with height + html += '<tr id="grid_'+ this.name + '_rec_top" line="top" style="height: '+ 0 +'px">'+ + ' <td colspan="200"></td>'+ + '</tr>'; + for (var i = 0; i < limit; i++) { + html += this.getRecordHTML(i, i+1); + } + html += '<tr id="grid_'+ this.name + '_rec_bottom" line="bottom" style="height: '+ ((buffered - limit) * this.recordHeight) +'px">'+ + ' <td colspan="200"></td>'+ + '</tr>'+ + '<tr id="grid_'+ this.name +'_rec_more" style="display: none">'+ + ' <td colspan="200" class="w2ui-load-more"></td>'+ + '</tr>'+ + '</table>'; + this.last.range_start = 0; + this.last.range_end = limit; + return html; + }, + + getSummaryHTML: function () { + if (this.summary.length == 0) return; + var html = '<table>'; + for (var i = 0; i < this.summary.length; i++) { + html += this.getRecordHTML(i, i+1, true); + } + html += '</table>'; + return html; + }, + + scroll: function (event) { + var time = (new Date()).getTime(); + var obj = this; + var records = $('#grid_'+ this.name +'_records'); + var buffered = this.records.length; + if (this.searchData.length != 0 && !this.url) buffered = this.last.searchIds.length; + if (buffered == 0 || records.length == 0 || records.height() == 0) return; + if (buffered > 300) this.show_extra = 30; else this.show_extra = 300; + // need this to enable scrolling when this.limit < then a screen can fit + if (records.height() < buffered * this.recordHeight && records.css('overflow-y') == 'hidden') { + if (this.total > 0) this.refresh(); + return; + } + // update footer + var t1 = Math.round(records[0].scrollTop / this.recordHeight + 1); + var t2 = t1 + (Math.round(records.height() / this.recordHeight) - 1); + if (t1 > buffered) t1 = buffered; + if (t2 > buffered) t2 = buffered; + var url = (typeof this.url != 'object' ? this.url : this.url.get); + $('#grid_'+ this.name + '_footer .w2ui-footer-right').html(w2utils.formatNumber(this.offset + t1) + '-' + w2utils.formatNumber(this.offset + t2) + ' ' + w2utils.lang('of') + ' ' + w2utils.formatNumber(this.total) + + (url ? ' ('+ w2utils.lang('buffered') + ' '+ w2utils.formatNumber(buffered) + (this.offset > 0 ? ', skip ' + w2utils.formatNumber(this.offset) : '') + ')' : '') + ); + // only for local data source, else no extra records loaded + if (!url && (!this.fixedBody || this.total <= 300)) return; + // regular processing + var start = Math.floor(records[0].scrollTop / this.recordHeight) - this.show_extra; + var end = start + Math.floor(records.height() / this.recordHeight) + this.show_extra * 2 + 1; + // var div = start - this.last.range_start; + if (start < 1) start = 1; + if (end > this.total) end = this.total; + var tr1 = records.find('#grid_'+ this.name +'_rec_top'); + var tr2 = records.find('#grid_'+ this.name +'_rec_bottom'); + // if row is expanded + if (String(tr1.next().prop('id')).indexOf('_expanded_row') != -1) tr1.next().remove(); + if (this.total > end && String(tr2.prev().prop('id')).indexOf('_expanded_row') != -1) tr2.prev().remove(); + var first = parseInt(tr1.next().attr('line')); + var last = parseInt(tr2.prev().attr('line')); + //$('#log').html('buffer: '+ this.buffered +' start-end: ' + start + '-'+ end + ' ===> first-last: ' + first + '-' + last); + if (first < start || first == 1 || this.last.pull_refresh) { // scroll down + // console.log('end', end, 'last', last, 'show_extre', this.show_extra, this.last.pull_refresh); + if (end <= last + this.show_extra - 2 && end != this.total) return; + this.last.pull_refresh = false; + // remove from top + while (true) { + var tmp = records.find('#grid_'+ this.name +'_rec_top').next(); + if (tmp.attr('line') == 'bottom') break; + if (parseInt(tmp.attr('line')) < start) tmp.remove(); else break; + } + // add at bottom + var tmp = records.find('#grid_'+ this.name +'_rec_bottom').prev(); + var rec_start = tmp.attr('line'); + if (rec_start == 'top') rec_start = start; + for (var i = parseInt(rec_start) + 1; i <= end; i++) { + if (!this.records[i-1]) continue; + if (this.records[i-1].expanded === true) this.records[i-1].expanded = false; + tr2.before(this.getRecordHTML(i-1, i)); + } + markSearch(); + setTimeout(function() { obj.refreshRanges(); }, 0); + } else { // scroll up + if (start >= first - this.show_extra + 2 && start > 1) return; + // remove from bottom + while (true) { + var tmp = records.find('#grid_'+ this.name +'_rec_bottom').prev(); + if (tmp.attr('line') == 'top') break; + if (parseInt(tmp.attr('line')) > end) tmp.remove(); else break; + } + // add at top + var tmp = records.find('#grid_'+ this.name +'_rec_top').next(); + var rec_start = tmp.attr('line'); + if (rec_start == 'bottom') rec_start = end; + for (var i = parseInt(rec_start) - 1; i >= start; i--) { + if (!this.records[i-1]) continue; + if (this.records[i-1].expanded === true) this.records[i-1].expanded = false; + tr1.after(this.getRecordHTML(i-1, i)); + } + markSearch(); + setTimeout(function() { obj.refreshRanges(); }, 0); + } + // first/last row size + var h1 = (start - 1) * obj.recordHeight; + var h2 = (buffered - end) * obj.recordHeight; + if (h2 < 0) h2 = 0; + tr1.css('height', h1 + 'px'); + tr2.css('height', h2 + 'px'); + obj.last.range_start = start; + obj.last.range_end = end; + // load more if needed + var s = Math.floor(records[0].scrollTop / this.recordHeight); + var e = s + Math.floor(records.height() / this.recordHeight); + if (e + 10 > buffered && this.last.pull_more !== true && buffered < this.total - this.offset) { + if (this.autoLoad === true) { + this.last.pull_more = true; + this.last.xhr_offset += this.limit; + this.request('get-records'); + } else { + var more = $('#grid_'+ this.name +'_rec_more'); + if (more.css('display') == 'none') { + more.show() + .on('click', function () { + obj.last.pull_more = true; + obj.last.xhr_offset += obj.limit; + obj.request('get-records'); + // show spinner the last + $(this).find('td').html('<div><div style="width: 20px; height: 20px;" class="w2ui-spinner"></div></div>'); + }); + } + if (more.find('td').text().indexOf('Load') == -1) { + more.find('td').html('<div>'+ w2utils.lang('Load') + ' ' + obj.limit + ' ' + w2utils.lang('More') + '...</div>'); + } + } + } + // check for grid end + if (buffered >= this.total - this.offset) $('#grid_'+ this.name +'_rec_more').hide(); + return; + + function markSearch() { + // mark search + if(obj.markSearch === false) return; + clearTimeout(obj.last.marker_timer); + obj.last.marker_timer = setTimeout(function () { + // mark all search strings + var str = []; + for (var s in obj.searchData) { + var tmp = obj.searchData[s]; + if ($.inArray(tmp.value, str) == -1) str.push(tmp.value); + } + if (str.length > 0) $(obj.box).find('.w2ui-grid-data > div').w2marker(str); + }, 50); + } + }, + + getRecordHTML: function (ind, lineNum, summary) { + var rec_html = ''; + var sel = this.last.selection; + var record; + // first record needs for resize purposes + if (ind == -1) { + rec_html += '<tr line="0">'; + if (this.show.lineNumbers) rec_html += '<td class="w2ui-col-number" style="height: 0px;"></td>'; + if (this.show.selectColumn) rec_html += '<td class="w2ui-col-select" style="height: 0px;"></td>'; + if (this.show.expandColumn) rec_html += '<td class="w2ui-col-expand" style="height: 0px;"></td>'; + for (var i in this.columns) { + if (this.columns[i].hidden) continue; + rec_html += '<td class="w2ui-grid-data" col="'+ i +'" style="height: 0px;"></td>'; + } + rec_html += '<td class="w2ui-grid-data-last" style="height: 0px;"></td>'; + rec_html += '</tr>'; + return rec_html; + } + // regular record + var url = (typeof this.url != 'object' ? this.url : this.url.get); + if (summary !== true) { + if (this.searchData.length > 0 && !url) { + if (ind >= this.last.searchIds.length) return ''; + ind = this.last.searchIds[ind]; + record = this.records[ind]; + } else { + if (ind >= this.records.length) return ''; + record = this.records[ind]; + } + } else { + if (ind >= this.summary.length) return ''; + record = this.summary[ind]; + } + if (!record) return ''; + var id = w2utils.escapeId(record.recid); + var isRowSelected = false; + if (sel.indexes.indexOf(ind) != -1) isRowSelected = true; + // render TR + rec_html += '<tr id="grid_'+ this.name +'_rec_'+ record.recid +'" recid="'+ record.recid +'" line="'+ lineNum +'" '+ + ' class="'+ (lineNum % 2 == 0 ? 'w2ui-even' : 'w2ui-odd') + (isRowSelected && this.selectType == 'row' ? ' w2ui-selected' : '') + (record.expanded === true ? ' w2ui-expanded' : '') + '" ' + + (summary !== true ? + (w2utils.isIOS ? + ' onclick = "w2ui[\''+ this.name +'\'].dblClick(\''+ record.recid +'\', event);"' + : + ' onclick = "w2ui[\''+ this.name +'\'].click(\''+ record.recid +'\', event);"'+ + ' oncontextmenu = "w2ui[\''+ this.name +'\'].contextMenu(\''+ record.recid +'\', event);"' + ) + : '' + ) + + ' style="height: '+ this.recordHeight +'px; '+ (!isRowSelected && typeof record['style'] == 'string' ? record['style'] : '') +'" '+ + ( typeof record['style'] == 'string' ? 'custom_style="'+ record['style'] +'"' : '') + + '>'; + if (this.show.lineNumbers) { + rec_html += '<td id="grid_'+ this.name +'_cell_'+ ind +'_number' + (summary ? '_s' : '') + '" class="w2ui-col-number">'+ + (summary !== true ? '<div>'+ lineNum +'</div>' : '') + + '</td>'; + } + if (this.show.selectColumn) { + rec_html += + '<td id="grid_'+ this.name +'_cell_'+ ind +'_select' + (summary ? '_s' : '') + '" class="w2ui-grid-data w2ui-col-select" '+ + ' onclick="if (event.stopPropagation) event.stopPropagation(); else event.cancelBubble = true;">'+ + (summary !== true ? + ' <div>'+ + ' <input class="w2ui-grid-select-check" type="checkbox" tabIndex="-1"'+ + ' '+ (isRowSelected ? 'checked="checked"' : '') + + ' onclick="var obj = w2ui[\''+ this.name +'\']; '+ + ' if (!obj.multiSelect) { obj.selectNone(); }'+ + ' if (this.checked) obj.select(\''+ record.recid + '\'); else obj.unselect(\''+ record.recid + '\'); '+ + ' if (event.stopPropagation) event.stopPropagation(); else event.cancelBubble = true;">'+ + ' </div>' + : + '' ) + + '</td>'; + } + if (this.show.expandColumn) { + var tmp_img = ''; + if (record.expanded === true) tmp_img = '-'; else tmp_img = '+'; + if (record.expanded == 'none') tmp_img = ''; + if (record.expanded == 'spinner') tmp_img = '<div class="w2ui-spinner" style="width: 16px; margin: -2px 2px;"></div>'; + rec_html += + '<td id="grid_'+ this.name +'_cell_'+ ind +'_expand' + (summary ? '_s' : '') + '" class="w2ui-grid-data w2ui-col-expand">'+ + (summary !== true ? + ' <div ondblclick="if (event.stopPropagation) event.stopPropagation(); else event.cancelBubble = true;" '+ + ' onclick="w2ui[\''+ this.name +'\'].toggle(\''+ record.recid +'\', event); '+ + ' if (event.stopPropagation) event.stopPropagation(); else event.cancelBubble = true;">'+ + ' '+ tmp_img +' </div>' + : + '' ) + + '</td>'; + } + var col_ind = 0; + while (true) { + var col = this.columns[col_ind]; + if (col.hidden) { col_ind++; if (typeof this.columns[col_ind] == 'undefined') break; else continue; } + var isChanged = !summary && record.changes && typeof record.changes[col.field] != 'undefined'; + var rec_cell = this.getCellHTML(ind, col_ind, summary); + var addStyle = ''; + if (typeof col.render == 'string') { + var tmp = col.render.toLowerCase().split(':'); + if (['number', 'int', 'float', 'money', 'currency', 'percent'].indexOf(tmp[0]) != -1) addStyle += 'text-align: right;'; + } + if (typeof record.style == 'object' && typeof record.style[col_ind] == 'string') { + addStyle += record.style[col_ind] + ';'; + } + var isCellSelected = false; + if (isRowSelected && $.inArray(col_ind, sel.columns[ind]) != -1) isCellSelected = true; + rec_html += '<td class="w2ui-grid-data'+ (isCellSelected ? ' w2ui-selected' : '') + (isChanged ? ' w2ui-changed' : '') +'" col="'+ col_ind +'" '+ + ' style="'+ addStyle + (typeof col.style != 'undefined' ? col.style : '') +'" '+ + (typeof col.attr != 'undefined' ? col.attr : '') +'>'+ + rec_cell + + '</td>'; + col_ind++; + if (typeof this.columns[col_ind] == 'undefined') break; + } + rec_html += '<td class="w2ui-grid-data-last"></td>'; + rec_html += '</tr>'; + return rec_html; + }, + + getCellHTML: function (ind, col_ind, summary) { + var col = this.columns[col_ind]; + var record = (summary !== true ? this.records[ind] : this.summary[ind]); + var data = this.getCellValue(ind, col_ind, summary); + var edit = col.editable; + // various renderers + if (typeof col.render != 'undefined') { + if (typeof col.render == 'function') { + data = $.trim(col.render.call(this, record, ind, col_ind)); + if (data.length < 4 || data.substr(0, 4).toLowerCase() != '<div') data = '<div>' + data + '</div>'; + } + if (typeof col.render == 'object') data = '<div>' + (col.render[data] || '') + '</div>'; + if (typeof col.render == 'string') { + var tmp = col.render.toLowerCase().split(':'); + var prefix = ''; + var suffix = ''; + if (['number', 'int', 'float', 'money', 'currency', 'percent'].indexOf(tmp[0]) != -1) { + if (typeof tmp[1] == 'undefined' || !w2utils.isInt(tmp[1])) tmp[1] = 0; + if (tmp[1] > 20) tmp[1] = 20; + if (tmp[1] < 0) tmp[1] = 0; + if (['money', 'currency'].indexOf(tmp[0]) != -1) { tmp[1] = w2utils.settings.currencyPrecision; prefix = w2utils.settings.currencyPrefix; suffix = w2utils.settings.currencySuffix } + if (tmp[0] == 'percent') { suffix = '%'; if (tmp[1] !== '0') tmp[1] = 1; } + if (tmp[0] == 'int') { tmp[1] = 0; } + // format + data = '<div>' + (data !== '' ? prefix + w2utils.formatNumber(Number(data).toFixed(tmp[1])) + suffix : '') + '</div>'; + } + if (tmp[0] == 'time') { + if (typeof tmp[1] == 'undefined' || tmp[1] == '') tmp[1] = w2utils.settings.time_format; + data = '<div>' + prefix + w2utils.formatTime(data, tmp[1] == 'h12' ? 'hh:mi pm': 'h24:min') + suffix + '</div>'; + } + if (tmp[0] == 'date') { + if (typeof tmp[1] == 'undefined' || tmp[1] == '') tmp[1] = w2utils.settings.date_display; + data = '<div>' + prefix + w2utils.formatDate(data, tmp[1]) + suffix + '</div>'; + } + if (tmp[0] == 'age') { + data = '<div>' + prefix + w2utils.age(data) + suffix + '</div>'; + } + if (tmp[0] == 'toggle') { + data = '<div>' + prefix + (data ? 'Yes' : '') + suffix + '</div>'; + } + } + } else { + // if editable checkbox + var addStyle = ''; + if (edit && ['checkbox', 'check'].indexOf(edit.type) != -1) { + var changeInd = summary ? -(ind + 1) : ind; + addStyle = 'text-align: center'; + data = '<input type="checkbox" '+ (data ? 'checked' : '') +' onclick="' + + ' var obj = w2ui[\''+ this.name + '\']; '+ + ' obj.editChange.call(obj, this, '+ changeInd +', '+ col_ind +', event); ' + + '">'; + } + if (!this.show.recordTitles) { + var data = '<div style="'+ addStyle +'">'+ data +'</div>'; + } else { + // title overwrite + var title = String(data).replace(/"/g, "''"); + if (typeof col.title != 'undefined') { + if (typeof col.title == 'function') title = col.title.call(this, record, ind, col_ind); + if (typeof col.title == 'string') title = col.title; + } + var data = '<div title="'+ w2utils.stripTags(title) +'" style="'+ addStyle +'">'+ data +'</div>'; + } + } + if (data == null || typeof data == 'undefined') data = ''; + return data; + }, + + getCellValue: function (ind, col_ind, summary) { + var col = this.columns[col_ind]; + var record = (summary !== true ? this.records[ind] : this.summary[ind]); + var data = this.parseField(record, col.field); + if (record.changes && typeof record.changes[col.field] != 'undefined') data = record.changes[col.field]; + if (data == null || typeof data == 'undefined') data = ''; + return data; + }, + + getFooterHTML: function () { + return '<div>'+ + ' <div class="w2ui-footer-left"></div>'+ + ' <div class="w2ui-footer-right"></div>'+ + ' <div class="w2ui-footer-center"></div>'+ + '</div>'; + }, + + status: function (msg) { + if (typeof msg != 'undefined') { + $('#grid_'+ this.name +'_footer').find('.w2ui-footer-left').html(msg); + } else { + // show number of selected + var msgLeft = ''; + var sel = this.getSelection(); + if (sel.length > 0) { + msgLeft = String(sel.length).replace(/(\d)(?=(\d\d\d)+(?!\d))/g, "$1,") + ' ' + w2utils.lang('selected'); + var tmp = sel[0]; + if (typeof tmp == 'object') tmp = tmp.recid + ', '+ w2utils.lang('Column') +': '+ tmp.column; + if (sel.length == 1) msgLeft = w2utils.lang('Record ID') + ': '+ tmp + ' '; + } + $('#grid_'+ this.name +'_footer .w2ui-footer-left').html(msgLeft); + // toolbar + if (sel.length == 1) this.toolbar.enable('w2ui-edit'); else this.toolbar.disable('w2ui-edit'); + if (sel.length >= 1) this.toolbar.enable('w2ui-delete'); else this.toolbar.disable('w2ui-delete'); + } + }, + + lock: function (msg, showSpinner) { + var box = $(this.box).find('> div:first-child'); + var args = Array.prototype.slice.call(arguments, 0); + args.unshift(box); + setTimeout(function () { w2utils.lock.apply(window, args); }, 10); + }, + + unlock: function () { + var box = this.box; + setTimeout(function () { w2utils.unlock(box); }, 25); // needed timer so if server fast, it will not flash + }, + + stateSave: function (returnOnly) { + if (!localStorage) return null; + var state = { + columns : [], + show : $.extend({}, this.show), + last : { + search : this.last.search, + multi : this.last.multi, + logic : this.last.logic, + caption : this.last.caption, + field : this.last.field, + scrollTop : this.last.scrollTop, + scrollLeft : this.last.scrollLeft + }, + sortData : [], + searchData : [] + }; + for (var i in this.columns) { + var col = this.columns[i]; + state.columns.push({ + field : col.field, + hidden : col.hidden, + size : col.size, + sizeCalculated : col.sizeCalculated, + sizeOriginal : col.sizeOriginal, + sizeType : col.sizeType + }); + } + for (var i in this.sortData) state.sortData.push($.extend({}, this.sortData[i])); + for (var i in this.searchData) state.searchData.push($.extend({}, this.searchData[i])); + // save into local storage + if (returnOnly !== true) { + // event before + var eventData = this.trigger({ phase: 'before', type: 'stateSave', target: this.name, state: state }); + if (eventData.isCancelled === true) { if (typeof callBack == 'function') callBack({ status: 'error', message: 'Request aborted.' }); return; } + try { + var savedState = $.parseJSON(localStorage.w2ui || '{}'); + if (!savedState) savedState = {}; + if (!savedState.states) savedState.states = {}; + savedState.states[this.name] = state; + localStorage.w2ui = JSON.stringify(savedState); + } catch (e) { + delete localStorage.w2ui; + return null; + } + // event after + this.trigger($.extend(eventData, { phase: 'after' })); + } + return state; + }, + + stateRestore: function (newState) { + var obj = this; + if (!newState) { + // read it from local storage + try { + if (!localStorage) return false; + var tmp = $.parseJSON(localStorage.w2ui || '{}'); + if (!tmp) tmp = {}; + if (!tmp.states) tmp.states = {}; + newState = tmp.states[this.name]; + } catch (e) { + delete localStorage.w2ui; + return null; + } + } + // event before + var eventData = this.trigger({ phase: 'before', type: 'stateRestore', target: this.name, state: newState }); + if (eventData.isCancelled === true) { if (typeof callBack == 'function') callBack({ status: 'error', message: 'Request aborted.' }); return; } + // default behavior + if ($.isPlainObject(newState)) { + $.extend(this.show, newState.show); + $.extend(this.last, newState.last); + var sTop = this.last.scrollTop; + var sLeft = this.last.scrollLeft; + for (var c in newState.columns) { + var tmp = newState.columns[c]; + var col = this.getColumn(tmp.field); + if (col) $.extend(col, tmp); + } + this.sortData.splice(0, this.sortData.length); + for (var c in newState.sortData) this.sortData.push(newState.sortData[c]); + this.searchData.splice(0, this.searchData.length); + for (var c in newState.searchData) this.searchData.push(newState.searchData[c]); + // apply sort and search + setTimeout(function () { + // needs timeout as records need to be populated + if (obj.sortData.length > 0) obj.localSort(); + if (obj.searchData.length > 0) obj.localSearch(); + obj.last.scrollTop = sTop; + obj.last.scrollLeft = sLeft; + obj.refresh(); + }, 1); + } + // event after + this.trigger($.extend(eventData, { phase: 'after' })); + return true; + }, + + stateReset: function () { + this.stateRestore(this.last.state); + // remove from local storage + if (localStorage) { + try { + var tmp = $.parseJSON(localStorage.w2ui || '{}'); + if (tmp.states && tmp.states[this.name]) { + delete tmp.states[this.name]; + } + localStorage.w2ui = JSON.stringify(tmp); + } catch (e) { + delete localStorage.w2ui; + return null; + } + } + }, + + parseField: function (obj, field) { + var val = ''; + try { // need this to make sure no error in fields + val = obj; + var tmp = String(field).split('.'); + for (var i in tmp) { + val = val[tmp[i]]; + } + } catch (event) { + val = ''; + } + return val; + }, + + prepareData: function () { + // loops thru records and prepares date and time objects + for (var r in this.records) { + var rec = this.records[r]; + for (var c in this.columns) { + var column = this.columns[c]; + if (rec[column.field] == null || typeof column.render != 'string') continue; + // number + if (['number', 'int', 'float', 'money', 'currency', 'percent'].indexOf(column.render.split(':')[0]) != -1) { + if (typeof rec[column.field] != 'number') rec[column.field] = parseFloat(rec[column.field]); + } + // date + if (['date', 'age'].indexOf(column.render.split(':')[0]) != -1) { + if (!rec[column.field + '_']) { + var dt = rec[column.field]; + if (w2utils.isInt(dt)) dt = parseInt(dt); + rec[column.field + '_'] = new Date(dt); + } + } + // time + if (['time'].indexOf(column.render) != -1) { + if (w2utils.isTime(rec[column.field])) { // if string + var tmp = w2utils.isTime(rec[column.field], true); + var dt = new Date(); + dt.setHours(tmp.hours, tmp.minutes, (tmp.seconds ? tmp.seconds : 0), 0); // sets hours, min, sec, mills + if (!rec[column.field + '_']) rec[column.field + '_'] = dt; + } else { // if date object + var tmp = rec[column.field]; + if (w2utils.isInt(tmp)) tmp = parseInt(tmp); + var tmp = (tmp != null ? new Date(tmp) : new Date()); + var dt = new Date(); + dt.setHours(tmp.getHours(), tmp.getMinutes(), tmp.getSeconds(), 0); // sets hours, min, sec, mills + if (!rec[column.field + '_']) rec[column.field + '_'] = dt; + } + } + } + } + }, + + nextCell: function (col_ind, editable) { + var check = col_ind + 1; + if (this.columns.length == check) return null; + if (editable === true) { + var edit = this.columns[check].editable; + if (this.columns[check].hidden || typeof edit == 'undefined' + || (edit && ['checkbox', 'check'].indexOf(edit.type) != -1)) return this.nextCell(check, editable); + } + return check; + }, + + prevCell: function (col_ind, editable) { + var check = col_ind - 1; + if (check < 0) return null; + if (editable === true) { + var edit = this.columns[check].editable; + if (this.columns[check].hidden || typeof edit == 'undefined' + || (edit && ['checkbox', 'check'].indexOf(edit.type) != -1)) return this.prevCell(check, editable); + } + return check; + }, + + nextRow: function (ind) { + if ((ind + 1 < this.records.length && this.last.searchIds.length == 0) // if there are more records + || (this.last.searchIds.length > 0 && ind < this.last.searchIds[this.last.searchIds.length-1])) { + ind++; + if (this.last.searchIds.length > 0) { + while (true) { + if ($.inArray(ind, this.last.searchIds) != -1 || ind > this.records.length) break; + ind++; + } + } + return ind; + } else { + return null; + } + }, + + prevRow: function (ind) { + if ((ind > 0 && this.last.searchIds.length == 0) // if there are more records + || (this.last.searchIds.length > 0 && ind > this.last.searchIds[0])) { + ind--; + if (this.last.searchIds.length > 0) { + while (true) { + if ($.inArray(ind, this.last.searchIds) != -1 || ind < 0) break; + ind--; + } + } + return ind; + } else { + return null; + } + } + }; + + $.extend(w2grid.prototype, w2utils.event); + w2obj.grid = w2grid; +})(); + +/************************************************************************ +* Library: Web 2.0 UI for jQuery (using prototypical inheritance) +* - Following objects defined +* - w2layout - layout widget +* - $().w2layout - jQuery wrapper +* - Dependencies: jQuery, w2utils, w2toolbar, w2tabs +* +* == NICE TO HAVE == +* - onResize for the panel +* - add more panel title positions (left=rotated, right=rotated, bottom) +* - bug: resizer is visible (and onHover) when panel is hidden. +* - bug: when you assign content before previous transition completed. +* +************************************************************************/ + +(function () { + var w2layout = function (options) { + this.box = null; // DOM Element that holds the element + this.name = null; // unique name for w2ui + this.panels = []; + this.tmp = {}; + + this.padding = 1; // panel padding + this.resizer = 4; // resizer width or height + this.style = ''; + + this.onShow = null; + this.onHide = null; + this.onResizing = null; + this.onResizerClick = null; + this.onRender = null; + this.onRefresh = null; + this.onResize = null; + this.onDestroy = null; + + $.extend(true, this, w2obj.layout, options); + }; + + /* @const */ var w2layout_panels = ['top', 'left', 'main', 'preview', 'right', 'bottom']; + + // ==================================================== + // -- Registers as a jQuery plugin + + $.fn.w2layout = function(method) { + if (typeof method === 'object' || !method ) { + // check name parameter + if (!w2utils.checkName(method, 'w2layout')) return; + var panels = method.panels || []; + var object = new w2layout(method); + $.extend(object, { handlers: [], panels: [] }); + // add defined panels + for (var p = 0, len = panels.length; p < len; p++) { + object.panels[p] = $.extend(true, {}, w2layout.prototype.panel, panels[p]); + if ($.isPlainObject(object.panels[p].tabs) || $.isArray(object.panels[p].tabs)) initTabs(object, panels[p].type); + if ($.isPlainObject(object.panels[p].toolbar) || $.isArray(object.panels[p].toolbar)) initToolbar(object, panels[p].type); + } + // add all other panels + for (var p1 in w2layout_panels) { + p1 = w2layout_panels[p1]; + if (object.get(p1) !== null) continue; + object.panels.push($.extend(true, {}, w2layout.prototype.panel, { type: p1, hidden: (p1 !== 'main'), size: 50 })); + } + if ($(this).length > 0) { + object.render($(this)[0]); + } + w2ui[object.name] = object; + return object; + + } else if (w2ui[$(this).attr('name')]) { + var obj = w2ui[$(this).attr('name')]; + obj[method].apply(obj, Array.prototype.slice.call(arguments, 1)); + return this; + } else { + console.log('ERROR: Method ' + method + ' does not exist on jQuery.w2layout' ); + } + + function initTabs(object, panel, tabs) { + var pan = object.get(panel); + if (pan !== null && typeof tabs == 'undefined') tabs = pan.tabs; + if (pan === null || tabs === null) return false; + // instanciate tabs + if ($.isArray(tabs)) tabs = { tabs: tabs }; + $().w2destroy(object.name + '_' + panel + '_tabs'); // destroy if existed + pan.tabs = $().w2tabs($.extend({}, tabs, { owner: object, name: object.name + '_' + panel + '_tabs' })); + pan.show.tabs = true; + return true; + } + + function initToolbar(object, panel, toolbar) { + var pan = object.get(panel); + if (pan !== null && typeof toolbar == 'undefined') toolbar = pan.toolbar; + if (pan === null || toolbar === null) return false; + // instanciate toolbar + if ($.isArray(toolbar)) toolbar = { items: toolbar }; + $().w2destroy(object.name + '_' + panel + '_toolbar'); // destroy if existed + pan.toolbar = $().w2toolbar($.extend({}, toolbar, { owner: object, name: object.name + '_' + panel + '_toolbar' })); + pan.show.toolbar = true; + return true; + } + }; + + // ==================================================== + // -- Implementation of core functionality + + w2layout.prototype = { + // default setting for a panel + panel: { + type : null, // left, right, top, bottom + title : '', + size : 100, // width or height depending on panel name + minSize : 20, + maxSize : false, + hidden : false, + resizable : false, + overflow : 'auto', + style : '', + content : '', // can be String or Object with .render(box) method + tabs : null, + toolbar : null, + width : null, // read only + height : null, // read only + show : { + toolbar : false, + tabs : false + }, + onRefresh : null, + onShow : null, + onHide : null + }, + + // alias for content + html: function (panel, data, transition) { + return this.content(panel, data, transition); + }, + + content: function (panel, data, transition) { + var obj = this; + var p = this.get(panel); + // if it is CSS panel + if (panel == 'css') { + $('#layout_'+ obj.name +'_panel_css').html('<style>'+ data +'</style>'); + return true; + } + if (p === null) return false; + if (typeof data == 'undefined' || data === null) { + return p.content; + } else { + if (data instanceof jQuery) { + console.log('ERROR: You can not pass jQuery object to w2layout.content() method'); + return false; + } + var pname = '#layout_'+ this.name + '_panel_'+ p.type; + var current = $(pname + '> .w2ui-panel-content'); + var panelTop = 0; + if (current.length > 0) { + $(pname).scrollTop(0); + panelTop = $(current).position().top; + } + if (p.content === '') { + p.content = data; + this.refresh(panel); + } else { + p.content = data; + if (!p.hidden) { + if (transition !== null && transition !== '' && typeof transition != 'undefined') { + // apply transition + var div1 = $(pname + '> .w2ui-panel-content'); + div1.after('<div class="w2ui-panel-content new-panel" style="'+ div1[0].style.cssText +'"></div>'); + var div2 = $(pname + '> .w2ui-panel-content.new-panel'); + div1.css('top', panelTop); + div2.css('top', panelTop); + if (typeof data == 'object') { + data.box = div2[0]; // do not do .render(box); + data.render(); + } else { + div2.html(data); + } + w2utils.transition(div1[0], div2[0], transition, function () { + div1.remove(); + div2.removeClass('new-panel'); + div2.css('overflow', p.overflow); + // IE Hack + obj.resize(); + if (window.navigator.userAgent.indexOf('MSIE') != -1) setTimeout(function () { obj.resize(); }, 100); + }); + } + } + this.refresh(panel); + } + } + // IE Hack + obj.resize(); + if (window.navigator.userAgent.indexOf('MSIE') != -1) setTimeout(function () { obj.resize(); }, 100); + return true; + }, + + load: function (panel, url, transition, onLoad) { + var obj = this; + if (panel == 'css') { + $.get(url, function (data, status, xhr) { // should always be $.get as it is template + obj.content(panel, xhr.responseText); + if (onLoad) onLoad(); + }); + return true; + } + if (this.get(panel) !== null) { + $.get(url, function (data, status, xhr) { // should always be $.get as it is template + obj.content(panel, xhr.responseText, transition); + if (onLoad) onLoad(); + // IE Hack + obj.resize(); + if (window.navigator.userAgent.indexOf('MSIE') != -1) setTimeout(function () { obj.resize(); }, 100); + }); + return true; + } + return false; + }, + + sizeTo: function (panel, size) { + var obj = this; + var pan = obj.get(panel); + if (pan === null) return false; + // resize + $(obj.box).find(' > div > .w2ui-panel').css({ + '-webkit-transition' : '.2s', + '-moz-transition' : '.2s', + '-ms-transition' : '.2s', + '-o-transition' : '.2s' + }); + setTimeout(function () { + obj.set(panel, { size: size }); + }, 1); + // clean + setTimeout(function () { + $(obj.box).find(' > div > .w2ui-panel').css({ + '-webkit-transition' : '0s', + '-moz-transition' : '0s', + '-ms-transition' : '0s', + '-o-transition' : '0s' + }); + obj.resize(); + }, 500); + return true; + }, + + show: function (panel, immediate) { + var obj = this; + // event before + var eventData = this.trigger({ phase: 'before', type: 'show', target: panel, object: this.get(panel), immediate: immediate }); + if (eventData.isCancelled === true) return; + + var p = obj.get(panel); + if (p === null) return false; + p.hidden = false; + if (immediate === true) { + $('#layout_'+ obj.name +'_panel_'+panel).css({ 'opacity': '1' }); + if (p.resizable) $('#layout_'+ obj.name +'_resizer_'+panel).show(); + obj.trigger($.extend(eventData, { phase: 'after' })); + obj.resize(); + } else { + if (p.resizable) $('#layout_'+ obj.name +'_resizer_'+panel).show(); + // resize + $('#layout_'+ obj.name +'_panel_'+panel).css({ 'opacity': '0' }); + $(obj.box).find(' > div > .w2ui-panel').css({ + '-webkit-transition' : '.2s', + '-moz-transition' : '.2s', + '-ms-transition' : '.2s', + '-o-transition' : '.2s' + }); + setTimeout(function () { obj.resize(); }, 1); + // show + setTimeout(function() { + $('#layout_'+ obj.name +'_panel_'+ panel).css({ 'opacity': '1' }); + }, 250); + // clean + setTimeout(function () { + $(obj.box).find(' > div > .w2ui-panel').css({ + '-webkit-transition' : '0s', + '-moz-transition' : '0s', + '-ms-transition' : '0s', + '-o-transition' : '0s' + }); + obj.trigger($.extend(eventData, { phase: 'after' })); + obj.resize(); + }, 500); + } + return true; + }, + + hide: function (panel, immediate) { + var obj = this; + // event before + var eventData = this.trigger({ phase: 'before', type: 'hide', target: panel, object: this.get(panel), immediate: immediate }); + if (eventData.isCancelled === true) return; + + var p = obj.get(panel); + if (p === null) return false; + p.hidden = true; + if (immediate === true) { + $('#layout_'+ obj.name +'_panel_'+panel).css({ 'opacity': '0' }); + $('#layout_'+ obj.name +'_resizer_'+panel).hide(); + obj.trigger($.extend(eventData, { phase: 'after' })); + obj.resize(); + } else { + $('#layout_'+ obj.name +'_resizer_'+panel).hide(); + // hide + $(obj.box).find(' > div > .w2ui-panel').css({ + '-webkit-transition' : '.2s', + '-moz-transition' : '.2s', + '-ms-transition' : '.2s', + '-o-transition' : '.2s' + }); + $('#layout_'+ obj.name +'_panel_'+panel).css({ 'opacity': '0' }); + setTimeout(function () { obj.resize(); }, 1); + // clean + setTimeout(function () { + $(obj.box).find(' > div > .w2ui-panel').css({ + '-webkit-transition' : '0s', + '-moz-transition' : '0s', + '-ms-transition' : '0s', + '-o-transition' : '0s' + }); + obj.trigger($.extend(eventData, { phase: 'after' })); + obj.resize(); + }, 500); + } + return true; + }, + + toggle: function (panel, immediate) { + var p = this.get(panel); + if (p === null) return false; + if (p.hidden) return this.show(panel, immediate); else return this.hide(panel, immediate); + }, + + set: function (panel, options) { + var obj = this.get(panel, true); + if (obj === null) return false; + $.extend(this.panels[obj], options); + if (typeof options['content'] != 'undefined') this.refresh(panel); // refresh only when content changed + this.resize(); // resize is needed when panel size is changed + return true; + }, + + get: function (panel, returnIndex) { + for (var p in this.panels) { + if (this.panels[p].type == panel) { + if (returnIndex === true) return p; else return this.panels[p]; + } + } + return null; + }, + + el: function (panel) { + var el = $('#layout_'+ this.name +'_panel_'+ panel +'> .w2ui-panel-content'); + if (el.length != 1) return null; + return el[0]; + }, + + hideToolbar: function (panel) { + var pan = this.get(panel); + if (!pan) return; + pan.show.toolbar = false; + $('#layout_'+ this.name +'_panel_'+ panel +'> .w2ui-panel-toolbar').hide(); + this.resize(); + }, + + showToolbar: function (panel) { + var pan = this.get(panel); + if (!pan) return; + pan.show.toolbar = true; + $('#layout_'+ this.name +'_panel_'+ panel +'> .w2ui-panel-toolbar').show(); + this.resize(); + }, + + toggleToolbar: function (panel) { + var pan = this.get(panel); + if (!pan) return; + if (pan.show.toolbar) this.hideToolbar(panel); else this.showToolbar(panel); + }, + + hideTabs: function (panel) { + var pan = this.get(panel); + if (!pan) return; + pan.show.tabs = false; + $('#layout_'+ this.name +'_panel_'+ panel +'> .w2ui-panel-tabs').hide(); + this.resize(); + }, + + showTabs: function (panel) { + var pan = this.get(panel); + if (!pan) return; + pan.show.tabs = true; + $('#layout_'+ this.name +'_panel_'+ panel +'> .w2ui-panel-tabs').show(); + this.resize(); + }, + + toggleTabs: function (panel) { + var pan = this.get(panel); + if (!pan) return; + if (pan.show.tabs) this.hideTabs(panel); else this.showTabs(panel); + }, + + render: function (box) { + var obj = this; + // if (window.getSelection) window.getSelection().removeAllRanges(); // clear selection + var time = (new Date()).getTime(); + // event before + var eventData = obj.trigger({ phase: 'before', type: 'render', target: obj.name, box: box }); + if (eventData.isCancelled === true) return; + + if (typeof box != 'undefined' && box !== null) { + if ($(obj.box).find('#layout_'+ obj.name +'_panel_main').length > 0) { + $(obj.box) + .removeAttr('name') + .removeClass('w2ui-layout') + .html(''); + } + obj.box = box; + } + if (!obj.box) return false; + $(obj.box) + .attr('name', obj.name) + .addClass('w2ui-layout') + .html('<div></div>'); + if ($(obj.box).length > 0) $(obj.box)[0].style.cssText += obj.style; + // create all panels + for (var p1 in w2layout_panels) { + p1 = w2layout_panels[p1]; + var pan = obj.get(p1); + var html = '<div id="layout_'+ obj.name + '_panel_'+ p1 +'" class="w2ui-panel">'+ + ' <div class="w2ui-panel-title"></div>'+ + ' <div class="w2ui-panel-tabs"></div>'+ + ' <div class="w2ui-panel-toolbar"></div>'+ + ' <div class="w2ui-panel-content"></div>'+ + '</div>'+ + '<div id="layout_'+ obj.name + '_resizer_'+ p1 +'" class="w2ui-resizer"></div>'; + $(obj.box).find(' > div').append(html); + // tabs are rendered in refresh() + } + $(obj.box).find(' > div') + .append('<div id="layout_'+ obj.name + '_panel_css" style="position: absolute; top: 10000px;"></div'); + obj.refresh(); // if refresh is not called here, the layout will not be available right after initialization + // process event + obj.trigger($.extend(eventData, { phase: 'after' })); + // reinit events + setTimeout(function () { // needed this timeout to allow browser to render first if there are tabs or toolbar + initEvents(); + obj.resize(); + }, 0); + return (new Date()).getTime() - time; + + function initEvents() { + obj.tmp.events = { + resize : function (event) { + w2ui[obj.name].resize(); + }, + resizeStart : resizeStart, + mouseMove : resizeMove, + mouseUp : resizeStop + }; + $(window).on('resize', obj.tmp.events.resize); + } + + function resizeStart(type, evnt) { + if (!obj.box) return; + if (!evnt) evnt = window.event; + if (!window.addEventListener) { window.document.attachEvent('onselectstart', function() { return false; } ); } + $(document).off('mousemove', obj.tmp.events.mouseMove).on('mousemove', obj.tmp.events.mouseMove); + $(document).off('mouseup', obj.tmp.events.mouseUp).on('mouseup', obj.tmp.events.mouseUp); + obj.tmp.resize = { + type : type, + x : evnt.screenX, + y : evnt.screenY, + diff_x : 0, + diff_y : 0, + value : 0 + }; + // lock all panels + for (var p1 in w2layout_panels) { + p1 = w2layout_panels[p1]; + obj.lock(p1, { opacity: 0 }); + } + if (type == 'left' || type == 'right') { + obj.tmp.resize.value = parseInt($('#layout_'+ obj.name +'_resizer_'+ type)[0].style.left); + } + if (type == 'top' || type == 'preview' || type == 'bottom') { + obj.tmp.resize.value = parseInt($('#layout_'+ obj.name +'_resizer_'+ type)[0].style.top); + } + } + + function resizeStop(evnt) { + if (!obj.box) return; + if (!evnt) evnt = window.event; + if (!window.addEventListener) { window.document.attachEvent('onselectstart', function() { return false; } ); } + $(document).off('mousemove', obj.tmp.events.mouseMove); + $(document).off('mouseup', obj.tmp.events.mouseUp); + if (typeof obj.tmp.resize == 'undefined') return; + // unlock all panels + for (var p1 in w2layout_panels) { + obj.unlock(w2layout_panels[p1]); + } + // set new size + if (obj.tmp.diff_x !== 0 || obj.tmp.resize.diff_y !== 0) { // only recalculate if changed + var ptop = obj.get('top'); + var pbottom = obj.get('bottom'); + var panel = obj.get(obj.tmp.resize.type); + var height = parseInt($(obj.box).height()); + var width = parseInt($(obj.box).width()); + var str = String(panel.size); + var ns, nd; + switch (obj.tmp.resize.type) { + case 'top': + ns = parseInt(panel.sizeCalculated) + obj.tmp.resize.diff_y; + nd = 0; + break; + case 'bottom': + ns = parseInt(panel.sizeCalculated) - obj.tmp.resize.diff_y; + nd = 0; + break; + case 'preview': + ns = parseInt(panel.sizeCalculated) - obj.tmp.resize.diff_y; + nd = (ptop && !ptop.hidden ? ptop.sizeCalculated : 0) + + (pbottom && !pbottom.hidden ? pbottom.sizeCalculated : 0); + break; + case 'left': + ns = parseInt(panel.sizeCalculated) + obj.tmp.resize.diff_x; + nd = 0; + break; + case 'right': + ns = parseInt(panel.sizeCalculated) - obj.tmp.resize.diff_x; + nd = 0; + break; + } + // set size + if (str.substr(str.length-1) == '%') { + panel.size = Math.floor(ns * 100 / + (panel.type == 'left' || panel.type == 'right' ? width : height - nd) * 100) / 100 + '%'; + } else { + panel.size = ns; + } + obj.resize(); + } + $('#layout_'+ obj.name + '_resizer_'+ obj.tmp.resize.type).removeClass('active'); + delete obj.tmp.resize; + } + + function resizeMove(evnt) { + if (!obj.box) return; + if (!evnt) evnt = window.event; + if (typeof obj.tmp.resize == 'undefined') return; + var panel = obj.get(obj.tmp.resize.type); + // event before + var tmp = obj.tmp.resize; + var eventData = obj.trigger({ phase: 'before', type: 'resizing', target: obj.name, object: panel, originalEvent: evnt, + panel: tmp ? tmp.type : 'all', diff_x: tmp ? tmp.diff_x : 0, diff_y: tmp ? tmp.diff_y : 0 }); + if (eventData.isCancelled === true) return; + + var p = $('#layout_'+ obj.name + '_resizer_'+ tmp.type); + var resize_x = (evnt.screenX - tmp.x); + var resize_y = (evnt.screenY - tmp.y); + var mainPanel = obj.get('main'); + + if (!p.hasClass('active')) p.addClass('active'); + + switch (tmp.type) { + case 'left': + if (panel.minSize - resize_x > panel.width) { + resize_x = panel.minSize - panel.width; + } + if (panel.maxSize && (panel.width + resize_x > panel.maxSize)) { + resize_x = panel.maxSize - panel.width; + } + if (mainPanel.minSize + resize_x > mainPanel.width) { + resize_x = mainPanel.width - mainPanel.minSize; + } + break; + + case 'right': + if (panel.minSize + resize_x > panel.width) { + resize_x = panel.width - panel.minSize; + } + if (panel.maxSize && (panel.width - resize_x > panel.maxSize)) { + resize_x = panel.width - panel.maxSize; + } + if (mainPanel.minSize - resize_x > mainPanel.width) { + resize_x = mainPanel.minSize - mainPanel.width; + } + break; + + case 'top': + if (panel.minSize - resize_y > panel.height) { + resize_y = panel.minSize - panel.height; + } + if (panel.maxSize && (panel.height + resize_y > panel.maxSize)) { + resize_y = panel.maxSize - panel.height; + } + if (mainPanel.minSize + resize_y > mainPanel.height) { + resize_y = mainPanel.height - mainPanel.minSize; + } + break; + + case 'preview': + case 'bottom': + if (panel.minSize + resize_y > panel.height) { + resize_y = panel.height - panel.minSize; + } + if (panel.maxSize && (panel.height - resize_y > panel.maxSize)) { + resize_y = panel.height - panel.maxSize; + } + if (mainPanel.minSize - resize_y > mainPanel.height) { + resize_y = mainPanel.minSize - mainPanel.height; + } + break; + } + tmp.diff_x = resize_x; + tmp.diff_y = resize_y; + + switch (tmp.type) { + case 'top': + case 'preview': + case 'bottom': + tmp.diff_x = 0; + if (p.length > 0) p[0].style.top = (tmp.value + tmp.diff_y) + 'px'; + break; + + case 'left': + case 'right': + tmp.diff_y = 0; + if (p.length > 0) p[0].style.left = (tmp.value + tmp.diff_x) + 'px'; + break; + } + // event after + obj.trigger($.extend(eventData, { phase: 'after' })); + } + }, + + refresh: function (panel) { + var obj = this; + // if (window.getSelection) window.getSelection().removeAllRanges(); // clear selection + if (typeof panel == 'undefined') panel = null; + var time = (new Date()).getTime(); + // event before + var eventData = obj.trigger({ phase: 'before', type: 'refresh', target: (typeof panel != 'undefined' ? panel : obj.name), object: obj.get(panel) }); + if (eventData.isCancelled === true) return; + // obj.unlock(panel); + if (typeof panel == 'string') { + var p = obj.get(panel); + if (p === null) return; + var pname = '#layout_'+ obj.name + '_panel_'+ p.type; + var rname = '#layout_'+ obj.name +'_resizer_'+ p.type; + // apply properties to the panel + $(pname).css({ display: p.hidden ? 'none' : 'block' }); + if (p.resizable) $(rname).show(); else $(rname).hide(); + // insert content + if (typeof p.content == 'object' && typeof p.content.render === 'function') { + p.content.box = $(pname +'> .w2ui-panel-content')[0]; + setTimeout(function () { + // need to remove unnecessary classes + if ($(pname +'> .w2ui-panel-content').length > 0) { + $(pname +'> .w2ui-panel-content') + .removeClass() + .addClass('w2ui-panel-content') + .css('overflow', p.overflow)[0].style.cssText += ';' + p.style; + } + p.content.render(); // do not do .render(box); + }, 1); + } else { + // need to remove unnecessary classes + if ($(pname +'> .w2ui-panel-content').length > 0) { + $(pname +'> .w2ui-panel-content') + .removeClass() + .addClass('w2ui-panel-content') + .html(p.content) + .css('overflow', p.overflow)[0].style.cssText += ';' + p.style; + } + } + // if there are tabs and/or toolbar - render it + var tmp = $(obj.box).find(pname +'> .w2ui-panel-tabs'); + if (p.show.tabs) { + if (tmp.find('[name='+ p.tabs.name +']').length === 0 && p.tabs !== null) tmp.w2render(p.tabs); else p.tabs.refresh(); + } else { + tmp.html('').removeClass('w2ui-tabs').hide(); + } + tmp = $(obj.box).find(pname +'> .w2ui-panel-toolbar'); + if (p.show.toolbar) { + if (tmp.find('[name='+ p.toolbar.name +']').length === 0 && p.toolbar !== null) tmp.w2render(p.toolbar); else p.toolbar.refresh(); + } else { + tmp.html('').removeClass('w2ui-toolbar').hide(); + } + // show title + tmp = $(obj.box).find(pname +'> .w2ui-panel-title'); + if (p.title) { + tmp.html(p.title).show(); + } else { + tmp.html('').hide(); + } + } else { + if ($('#layout_'+ obj.name +'_panel_main').length == 0) { + obj.render(); + return; + } + obj.resize(); + // refresh all of them + for (var p1 in this.panels) { obj.refresh(this.panels[p1].type); } + } + obj.trigger($.extend(eventData, { phase: 'after' })); + return (new Date()).getTime() - time; + }, + + resize: function () { + // if (window.getSelection) window.getSelection().removeAllRanges(); // clear selection + if (!this.box) return false; + var time = (new Date()).getTime(); + // event before + var tmp = this.tmp.resize; + var eventData = this.trigger({ phase: 'before', type: 'resize', target: this.name, + panel: tmp ? tmp.type : 'all', diff_x: tmp ? tmp.diff_x : 0, diff_y: tmp ? tmp.diff_y : 0 }); + if (eventData.isCancelled === true) return; + if (this.padding < 0) this.padding = 0; + + // layout itself + var width = parseInt($(this.box).width()); + var height = parseInt($(this.box).height()); + $(this.box).find(' > div').css({ + width : width + 'px', + height : height + 'px' + }); + var obj = this; + // panels + var pmain = this.get('main'); + var pprev = this.get('preview'); + var pleft = this.get('left'); + var pright = this.get('right'); + var ptop = this.get('top'); + var pbottom = this.get('bottom'); + var smain = true; // main always on + var sprev = (pprev !== null && pprev.hidden !== true ? true : false); + var sleft = (pleft !== null && pleft.hidden !== true ? true : false); + var sright = (pright !== null && pright.hidden !== true ? true : false); + var stop = (ptop !== null && ptop.hidden !== true ? true : false); + var sbottom = (pbottom !== null && pbottom.hidden !== true ? true : false); + var l, t, w, h, e; + // calculate % + for (var p in w2layout_panels) { + p = w2layout_panels[p]; + if (p === 'main') continue; + var tmp = this.get(p); + if (!tmp) continue; + var str = String(tmp.size || 0); + if (str.substr(str.length-1) == '%') { + var tmph = height; + if (tmp.type == 'preview') { + tmph = tmph - + (ptop && !ptop.hidden ? ptop.sizeCalculated : 0) - + (pbottom && !pbottom.hidden ? pbottom.sizeCalculated : 0); + } + tmp.sizeCalculated = parseInt((tmp.type == 'left' || tmp.type == 'right' ? width : tmph) * parseFloat(tmp.size) / 100); + } else { + tmp.sizeCalculated = parseInt(tmp.size); + } + tmp.sizeCalculated = Math.max(tmp.sizeCalculated, parseInt(tmp.minSize)); + } + // top if any + if (ptop !== null && ptop.hidden !== true) { + l = 0; + t = 0; + w = width; + h = ptop.sizeCalculated; + $('#layout_'+ this.name +'_panel_top').css({ + 'display': 'block', + 'left': l + 'px', + 'top': t + 'px', + 'width': w + 'px', + 'height': h + 'px' + }).show(); + ptop.width = w; + ptop.height = h; + // resizer + if (ptop.resizable) { + t = ptop.sizeCalculated - (this.padding === 0 ? this.resizer : 0); + h = (this.resizer > this.padding ? this.resizer : this.padding); + $('#layout_'+ this.name +'_resizer_top').show().css({ + 'display': 'block', + 'left': l + 'px', + 'top': t + 'px', + 'width': w + 'px', + 'height': h + 'px', + 'cursor': 'ns-resize' + }).off('mousedown').on('mousedown', function (event) { + // event before + var eventData = obj.trigger({ phase: 'before', type: 'resizerClick', target: 'top', originalEvent: event }); + if (eventData.isCancelled === true) return; + // default action + w2ui[obj.name].tmp.events.resizeStart('top', event); + // event after + obj.trigger($.extend(eventData, { phase: 'after' })); + return false; + }); + } + } else { + $('#layout_'+ this.name +'_panel_top').hide(); + } + // left if any + if (pleft !== null && pleft.hidden !== true) { + l = 0; + t = 0 + (stop ? ptop.sizeCalculated + this.padding : 0); + w = pleft.sizeCalculated; + h = height - (stop ? ptop.sizeCalculated + this.padding : 0) - + (sbottom ? pbottom.sizeCalculated + this.padding : 0); + e = $('#layout_'+ this.name +'_panel_left'); + if (window.navigator.userAgent.indexOf('MSIE') != -1 && e.length > 0 && e[0].clientHeight < e[0].scrollHeight) w += 17; // IE hack + e.css({ + 'display': 'block', + 'left': l + 'px', + 'top': t + 'px', + 'width': w + 'px', + 'height': h + 'px' + }).show(); + pleft.width = w; + pleft.height = h; + // resizer + if (pleft.resizable) { + l = pleft.sizeCalculated - (this.padding === 0 ? this.resizer : 0); + w = (this.resizer > this.padding ? this.resizer : this.padding); + $('#layout_'+ this.name +'_resizer_left').show().css({ + 'display': 'block', + 'left': l + 'px', + 'top': t + 'px', + 'width': w + 'px', + 'height': h + 'px', + 'cursor': 'ew-resize' + }).off('mousedown').on('mousedown', function (event) { + // event before + var eventData = obj.trigger({ phase: 'before', type: 'resizerClick', target: 'left', originalEvent: event }); + if (eventData.isCancelled === true) return; + // default action + w2ui[obj.name].tmp.events.resizeStart('left', event); + // event after + obj.trigger($.extend(eventData, { phase: 'after' })); + return false; + }); + } + } else { + $('#layout_'+ this.name +'_panel_left').hide(); + $('#layout_'+ this.name +'_resizer_left').hide(); + } + // right if any + if (pright !== null && pright.hidden !== true) { + l = width - pright.sizeCalculated; + t = 0 + (stop ? ptop.sizeCalculated + this.padding : 0); + w = pright.sizeCalculated; + h = height - (stop ? ptop.sizeCalculated + this.padding : 0) - + (sbottom ? pbottom.sizeCalculated + this.padding : 0); + $('#layout_'+ this.name +'_panel_right').css({ + 'display': 'block', + 'left': l + 'px', + 'top': t + 'px', + 'width': w + 'px', + 'height': h + 'px' + }).show(); + pright.width = w; + pright.height = h; + // resizer + if (pright.resizable) { + l = l - this.padding; + w = (this.resizer > this.padding ? this.resizer : this.padding); + $('#layout_'+ this.name +'_resizer_right').show().css({ + 'display': 'block', + 'left': l + 'px', + 'top': t + 'px', + 'width': w + 'px', + 'height': h + 'px', + 'cursor': 'ew-resize' + }).off('mousedown').on('mousedown', function (event) { + // event before + var eventData = obj.trigger({ phase: 'before', type: 'resizerClick', target: 'right', originalEvent: event }); + if (eventData.isCancelled === true) return; + // default action + w2ui[obj.name].tmp.events.resizeStart('right', event); + // event after + obj.trigger($.extend(eventData, { phase: 'after' })); + return false; + }); + } + } else { + $('#layout_'+ this.name +'_panel_right').hide(); + } + // bottom if any + if (pbottom !== null && pbottom.hidden !== true) { + l = 0; + t = height - pbottom.sizeCalculated; + w = width; + h = pbottom.sizeCalculated; + $('#layout_'+ this.name +'_panel_bottom').css({ + 'display': 'block', + 'left': l + 'px', + 'top': t + 'px', + 'width': w + 'px', + 'height': h + 'px' + }).show(); + pbottom.width = w; + pbottom.height = h; + // resizer + if (pbottom.resizable) { + t = t - (this.padding === 0 ? 0 : this.padding); + h = (this.resizer > this.padding ? this.resizer : this.padding); + $('#layout_'+ this.name +'_resizer_bottom').show().css({ + 'display': 'block', + 'left': l + 'px', + 'top': t + 'px', + 'width': w + 'px', + 'height': h + 'px', + 'cursor': 'ns-resize' + }).off('mousedown').on('mousedown', function (event) { + // event before + var eventData = obj.trigger({ phase: 'before', type: 'resizerClick', target: 'bottom', originalEvent: event }); + if (eventData.isCancelled === true) return; + // default action + w2ui[obj.name].tmp.events.resizeStart('bottom', event); + // event after + obj.trigger($.extend(eventData, { phase: 'after' })); + return false; + }); + } + } else { + $('#layout_'+ this.name +'_panel_bottom').hide(); + } + // main - always there + l = 0 + (sleft ? pleft.sizeCalculated + this.padding : 0); + t = 0 + (stop ? ptop.sizeCalculated + this.padding : 0); + w = width - (sleft ? pleft.sizeCalculated + this.padding : 0) - + (sright ? pright.sizeCalculated + this.padding: 0); + h = height - (stop ? ptop.sizeCalculated + this.padding : 0) - + (sbottom ? pbottom.sizeCalculated + this.padding : 0) - + (sprev ? pprev.sizeCalculated + this.padding : 0); + e = $('#layout_'+ this.name +'_panel_main'); + if (window.navigator.userAgent.indexOf('MSIE') != -1 && e.length > 0 && e[0].clientHeight < e[0].scrollHeight) w += 17; // IE hack + e.css({ + 'display': 'block', + 'left': l + 'px', + 'top': t + 'px', + 'width': w + 'px', + 'height': h + 'px' + }); + pmain.width = w; + pmain.height = h; + + // preview if any + if (pprev !== null && pprev.hidden !== true) { + l = 0 + (sleft ? pleft.sizeCalculated + this.padding : 0); + t = height - (sbottom ? pbottom.sizeCalculated + this.padding : 0) - pprev.sizeCalculated; + w = width - (sleft ? pleft.sizeCalculated + this.padding : 0) - + (sright ? pright.sizeCalculated + this.padding : 0); + h = pprev.sizeCalculated; + e = $('#layout_'+ this.name +'_panel_preview'); + if (window.navigator.userAgent.indexOf('MSIE') != -1 && e.length > 0 && e[0].clientHeight < e[0].scrollHeight) w += 17; // IE hack + e.css({ + 'display': 'block', + 'left': l + 'px', + 'top': t + 'px', + 'width': w + 'px', + 'height': h + 'px' + }).show(); + pprev.width = w; + pprev.height = h; + // resizer + if (pprev.resizable) { + t = t - (this.padding === 0 ? 0 : this.padding); + h = (this.resizer > this.padding ? this.resizer : this.padding); + $('#layout_'+ this.name +'_resizer_preview').show().css({ + 'display': 'block', + 'left': l + 'px', + 'top': t + 'px', + 'width': w + 'px', + 'height': h + 'px', + 'cursor': 'ns-resize' + }).off('mousedown').on('mousedown', function (event) { + // event before + var eventData = obj.trigger({ phase: 'before', type: 'resizerClick', target: 'preview', originalEvent: event }); + if (eventData.isCancelled === true) return; + // default action + w2ui[obj.name].tmp.events.resizeStart('preview', event); + // event after + obj.trigger($.extend(eventData, { phase: 'after' })); + return false; + }); + } + } else { + $('#layout_'+ this.name +'_panel_preview').hide(); + } + + // display tabs and toolbar if needed + for (var p1 in w2layout_panels) { + p1 = w2layout_panels[p1]; + var pan = this.get(p1); + var tmp2 = '#layout_'+ this.name +'_panel_'+ p1 +' > .w2ui-panel-'; + var tabHeight = 0; + if (pan) { + if (pan.title) { + tabHeight += w2utils.getSize($(tmp2 + 'title').css({ top: tabHeight + 'px', display: 'block' }), 'height'); + } + if (pan.show.tabs) { + if (pan.tabs !== null && w2ui[this.name +'_'+ p1 +'_tabs']) w2ui[this.name +'_'+ p1 +'_tabs'].resize(); + tabHeight += w2utils.getSize($(tmp2 + 'tabs').css({ top: tabHeight + 'px', display: 'block' }), 'height'); + } + if (pan.show.toolbar) { + if (pan.toolbar !== null && w2ui[this.name +'_'+ p1 +'_toolbar']) w2ui[this.name +'_'+ p1 +'_toolbar'].resize(); + tabHeight += w2utils.getSize($(tmp2 + 'toolbar').css({ top: tabHeight + 'px', display: 'block' }), 'height'); + } + } + $(tmp2 + 'content').css({ display: 'block' }).css({ top: tabHeight + 'px' }); + } + // send resize to all objects + clearTimeout(this._resize_timer); + this._resize_timer = setTimeout(function () { + for (var e in w2ui) { + if (typeof w2ui[e].resize == 'function') { + // sent to all none-layouts + if (w2ui[e].panels == 'undefined') w2ui[e].resize(); + // only send to nested layouts + var parent = $(w2ui[e].box).parents('.w2ui-layout'); + if (parent.length > 0 && parent.attr('name') == obj.name) w2ui[e].resize(); + } + } + }, 100); + this.trigger($.extend(eventData, { phase: 'after' })); + return (new Date()).getTime() - time; + }, + + destroy: function () { + // event before + var eventData = this.trigger({ phase: 'before', type: 'destroy', target: this.name }); + if (eventData.isCancelled === true) return; + if (typeof w2ui[this.name] == 'undefined') return false; + // clean up + if ($(this.box).find('#layout_'+ this.name +'_panel_main').length > 0) { + $(this.box) + .removeAttr('name') + .removeClass('w2ui-layout') + .html(''); + } + delete w2ui[this.name]; + // event after + this.trigger($.extend(eventData, { phase: 'after' })); + if (this.tmp.events && this.tmp.events.resize) $(window).off('resize', this.tmp.events.resize); + return true; + }, + + lock: function (panel, msg, showSpinner) { + if (w2layout_panels.indexOf(panel) == -1) { + console.log('ERROR: First parameter needs to be the a valid panel name.'); + return; + } + var args = Array.prototype.slice.call(arguments, 0); + args[0] = '#layout_'+ this.name + '_panel_' + panel; + w2utils.lock.apply(window, args); + }, + + unlock: function (panel) { + if (w2layout_panels.indexOf(panel) == -1) { + console.log('ERROR: First parameter needs to be the a valid panel name.'); + return; + } + var nm = '#layout_'+ this.name + '_panel_' + panel; + w2utils.unlock(nm); + } + }; + + $.extend(w2layout.prototype, w2utils.event); + w2obj.layout = w2layout; +})(); + +/************************************************************************ +* Library: Web 2.0 UI for jQuery (using prototypical inheritance) +* - Following objects defined +* - w2popup - popup widget +* - $().w2popup - jQuery wrapper +* - Dependencies: jQuery, w2utils +* +* == NICE TO HAVE == +* - transition should include title, body and buttons, not just body +* +************************************************************************/ + +var w2popup = {}; + +(function () { + + // ==================================================== + // -- Registers as a jQuery plugin + + $.fn.w2popup = function(method, options) { + if (typeof method === 'undefined') { + options = {}; + method = 'open'; + } + if ($.isPlainObject(method)) { + options = method; + method = 'open'; + } + method = method.toLowerCase(); + if (method === 'load' && typeof options === 'string') { + options = $.extend({ url: options }, arguments.length > 2 ? arguments[2] : {}); + } + if (method === 'open' && options.url != null) method = 'load'; + options = options || {}; + // load options from markup + var dlgOptions = {}; + if ($(this).length > 0) { + if ($(this).find('div[rel=title], div[rel=body], div[rel=buttons]').length > 0) { + if ($(this).find('div[rel=title]').length > 0) { + dlgOptions['title'] = $(this).find('div[rel=title]').html(); + } + if ($(this).find('div[rel=body]').length > 0) { + dlgOptions['body'] = $(this).find('div[rel=body]').html(); + dlgOptions['style'] = $(this).find('div[rel=body]')[0].style.cssText; + } + if ($(this).find('div[rel=buttons]').length > 0) { + dlgOptions['buttons'] = $(this).find('div[rel=buttons]').html(); + } + } else { + dlgOptions['title'] = ' '; + dlgOptions['body'] = $(this).html(); + } + if (parseInt($(this).css('width')) != 0) dlgOptions['width'] = parseInt($(this).css('width')); + if (parseInt($(this).css('height')) != 0) dlgOptions['height'] = parseInt($(this).css('height')); + } + // show popup + return w2popup[method]($.extend({}, dlgOptions, options)); + }; + + // ==================================================== + // -- Implementation of core functionality (SINGELTON) + + w2popup = { + defaults: { + title : '', + body : '', + buttons : '', + style : '', + color : '#000', + opacity : 0.4, + speed : 0.3, + modal : false, + maximized : false, + keyboard : true, // will close popup on esc if not modal + width : 500, + height : 300, + showClose : true, + showMax : false, + transition: null + }, + status : 'closed', // string that describes current status + handlers : [], + onOpen : null, + onClose : null, + onMax : null, + onMin : null, + onToggle : null, + onKeydown : null, + + open: function (options) { + var obj = this; + if (w2popup.status == 'closing') { + setTimeout(function () { obj.open.call(obj, options); }, 100); + return; + } + // get old options and merge them + var old_options = $('#w2ui-popup').data('options'); + var options = $.extend({}, this.defaults, old_options, { title: '', body : '', buttons: '' }, options, { maximized: false }); + // need timer because popup might not be open + setTimeout(function () { $('#w2ui-popup').data('options', options); }, 100); + // if new - reset event handlers + if ($('#w2ui-popup').length == 0) { + w2popup.handlers = []; + w2popup.onMax = null; + w2popup.onMin = null; + w2popup.onToggle = null; + w2popup.onOpen = null; + w2popup.onClose = null; + w2popup.onKeydown = null; + } + if (options.onOpen) w2popup.onOpen = options.onOpen; + if (options.onClose) w2popup.onClose = options.onClose; + if (options.onMax) w2popup.onMax = options.onMax; + if (options.onMin) w2popup.onMin = options.onMin; + if (options.onToggle) w2popup.onToggle = options.onToggle; + if (options.onKeydown) w2popup.onKeydown = options.onKeydown; + + if (window.innerHeight == undefined) { + var width = document.documentElement.offsetWidth; + var height = document.documentElement.offsetHeight; + if (w2utils.engine === 'IE7') { width += 21; height += 4; } + } else { + var width = window.innerWidth; + var height = window.innerHeight; + } + if (parseInt(width) - 10 < parseInt(options.width)) options.width = parseInt(width) - 10; + if (parseInt(height) - 10 < parseInt(options.height)) options.height = parseInt(height) - 10; + var top = parseInt(((parseInt(height) - parseInt(options.height)) / 2) * 0.6); + var left = parseInt((parseInt(width) - parseInt(options.width)) / 2); + // check if message is already displayed + if ($('#w2ui-popup').length == 0) { + // trigger event + var eventData = this.trigger({ phase: 'before', type: 'open', target: 'popup', options: options, present: false }); + if (eventData.isCancelled === true) return; + w2popup.status = 'opening'; + // output message + w2popup.lockScreen(options); + var btn = ''; + if (options.showClose) { + btn += '<div class="w2ui-msg-button w2ui-msg-close" onmousedown="event.stopPropagation()" onclick="w2popup.close()">Close</div>'; + } + if (options.showMax) { + btn += '<div class="w2ui-msg-button w2ui-msg-max" onmousedown="event.stopPropagation()" onclick="w2popup.toggle()">Max</div>'; + } + var msg='<div id="w2ui-popup" class="w2ui-popup" style="opacity: 0; left: '+ left +'px; top: '+ top +'px;'+ + ' width: ' + parseInt(options.width) + 'px; height: ' + parseInt(options.height) + 'px; '+ + ' -webkit-transform: scale(0.8); -moz-transform: scale(0.8); -ms-transform: scale(0.8); -o-transform: scale(0.8); "'+ + '>'+ + ' <div class="w2ui-msg-title" style="'+ (options.title == '' ? 'display: none' : '') +'">' + btn + options.title + '</div>'+ + ' <div class="w2ui-box1" style="'+ (options.title == '' ? 'top: 0px !important;' : '') + + (options.buttons == '' ? 'bottom: 0px !important;' : '') + '">'+ + ' <div class="w2ui-msg-body' + (!options.title != '' ? ' w2ui-msg-no-title' : '') + + (!options.buttons != '' ? ' w2ui-msg-no-buttons' : '') + '" style="' + options.style + '">' + options.body + '</div>'+ + ' </div>'+ + ' <div class="w2ui-box2" style="' + (options.title == '' ? 'top: 0px !important;' : '') + + (options.buttons == '' ? 'bottom: 0px !important;' : '') + '">'+ + ' <div class="w2ui-msg-body' + (!options.title != '' ? ' w2ui-msg-no-title' : '') + + (!options.buttons != '' ? ' w2ui-msg-no-buttons' : '') + '" style="' + options.style + '"></div>'+ + ' </div>'+ + ' <div class="w2ui-msg-buttons" style="'+ (options.buttons == '' ? 'display: none' : '') +'">' + options.buttons + '</div>'+ + '</div>'; + $('body').append(msg); + // allow element to render + setTimeout(function () { + $('#w2ui-popup .w2ui-box2').hide(); + $('#w2ui-popup').css({ + '-webkit-transition': options.speed + 's opacity, ' + options.speed + 's -webkit-transform', + '-webkit-transform': 'scale(1)', + '-moz-transition': options.speed + 's opacity, ' + options.speed + 's -moz-transform', + '-moz-transform': 'scale(1)', + '-ms-transition': options.speed + 's opacity, ' + options.speed + 's -ms-transform', + '-ms-transform': 'scale(1)', + '-o-transition': options.speed + 's opacity, ' + options.speed + 's -o-transform', + '-o-transform': 'scale(1)', + 'opacity': '1' + }); + }, 1); + // clean transform + setTimeout(function () { + $('#w2ui-popup').css({ + '-webkit-transform': '', + '-moz-transform': '', + '-ms-transform': '', + '-o-transform': '' + }); + // event after + w2popup.status = 'open'; + setTimeout(function () { + obj.trigger($.extend(eventData, { phase: 'after' })); + }, 100); + }, options.speed * 1000); + } else { + // trigger event + var eventData = this.trigger({ phase: 'before', type: 'open', target: 'popup', options: options, present: true }); + if (eventData.isCancelled === true) return; + // check if size changed + w2popup.status = 'opening'; + if (typeof old_options == 'undefined' || old_options['width'] != options['width'] || old_options['height'] != options['height']) { + w2popup.resize(options.width, options.height); + } + if (typeof old_options != 'undefined') { + options.prevSize = options.width + ':' + options.height; + options.maximized = old_options.maximized; + } + // show new items + var body = $('#w2ui-popup .w2ui-box2 > .w2ui-msg-body').html(options.body); + if (body.length > 0) body[0].style.cssText = options.style; + if (options.buttons != '') { + $('#w2ui-popup .w2ui-msg-buttons').show().html(options.buttons); + $('#w2ui-popup .w2ui-msg-body').removeClass('w2ui-msg-no-buttons'); + $('#w2ui-popup .w2ui-box1, #w2ui-popup .w2ui-box2').css('bottom', ''); + } else { + $('#w2ui-popup .w2ui-msg-buttons').hide().html(''); + $('#w2ui-popup .w2ui-msg-body').addClass('w2ui-msg-no-buttons'); + $('#w2ui-popup .w2ui-box1, #w2ui-popup .w2ui-box2').css('bottom', '0px'); + } + if (options.title != '') { + $('#w2ui-popup .w2ui-msg-title').show().html( + (options.showClose ? '<div class="w2ui-msg-button w2ui-msg-close" onmousedown="event.stopPropagation()" onclick="w2popup.close()">Close</div>' : '') + + (options.showMax ? '<div class="w2ui-msg-button w2ui-msg-max" onmousedown="event.stopPropagation()" onclick="w2popup.toggle()">Max</div>' : '') + + options.title); + $('#w2ui-popup .w2ui-msg-body').removeClass('w2ui-msg-no-title'); + $('#w2ui-popup .w2ui-box1, #w2ui-popup .w2ui-box2').css('top', ''); + } else { + $('#w2ui-popup .w2ui-msg-title').hide().html(''); + $('#w2ui-popup .w2ui-msg-body').addClass('w2ui-msg-no-title'); + $('#w2ui-popup .w2ui-box1, #w2ui-popup .w2ui-box2').css('top', '0px'); + } + // transition + var div_old = $('#w2ui-popup .w2ui-box1')[0]; + var div_new = $('#w2ui-popup .w2ui-box2')[0]; + w2utils.transition(div_old, div_new, options.transition); + div_new.className = 'w2ui-box1'; + div_old.className = 'w2ui-box2'; + $(div_new).addClass('w2ui-current-box'); + // remove max state + $('#w2ui-popup').data('prev-size', null); + // call event onChange + setTimeout(function () { + w2popup.status = 'open'; + obj.trigger($.extend(eventData, { phase: 'after' })); + }, 100); + } + // save new options + options._last_w2ui_name = w2utils.keyboard.active(); + w2utils.keyboard.active(null); + // keyboard events + if (options.keyboard) $(document).on('keydown', this.keydown); + + // initialize move + var tmp = { + resizing : false, + mvMove : mvMove, + mvStop : mvStop + }; + $('#w2ui-popup .w2ui-msg-title').on('mousedown', function (event) { mvStart(event); }) + + // handlers + function mvStart(evnt) { + if (!evnt) evnt = window.event; + if (!window.addEventListener) { window.document.attachEvent('onselectstart', function() { return false; } ); } + w2popup.status = 'moving'; + tmp.resizing = true; + tmp.x = evnt.screenX; + tmp.y = evnt.screenY; + tmp.pos_x = $('#w2ui-popup').position().left; + tmp.pos_y = $('#w2ui-popup').position().top; + w2popup.lock({ opacity: 0 }); + $(document).on('mousemove', tmp.mvMove); + $(document).on('mouseup', tmp.mvStop); + if (evnt.stopPropagation) evnt.stopPropagation(); else evnt.cancelBubble = true; + if (evnt.preventDefault) evnt.preventDefault(); else return false; + } + + function mvMove(evnt) { + if (tmp.resizing != true) return; + if (!evnt) evnt = window.event; + tmp.div_x = evnt.screenX - tmp.x; + tmp.div_y = evnt.screenY - tmp.y; + $('#w2ui-popup').css({ + '-webkit-transition': 'none', + '-webkit-transform': 'translate3d('+ tmp.div_x +'px, '+ tmp.div_y +'px, 0px)', + '-moz-transition': 'none', + '-moz-transform': 'translate('+ tmp.div_x +'px, '+ tmp.div_y +'px)', + '-ms-transition': 'none', + '-ms-transform': 'translate('+ tmp.div_x +'px, '+ tmp.div_y +'px)', + '-o-transition': 'none', + '-o-transform': 'translate('+ tmp.div_x +'px, '+ tmp.div_y +'px)' + }); + } + + function mvStop(evnt) { + if (tmp.resizing != true) return; + if (!evnt) evnt = window.event; + w2popup.status = 'open'; + tmp.div_x = (evnt.screenX - tmp.x); + tmp.div_y = (evnt.screenY - tmp.y); + $('#w2ui-popup').css({ + 'left': (tmp.pos_x + tmp.div_x) + 'px', + 'top': (tmp.pos_y + tmp.div_y) + 'px', + '-webkit-transition': 'none', + '-webkit-transform': 'translate3d(0px, 0px, 0px)', + '-moz-transition': 'none', + '-moz-transform': 'translate(0px, 0px)', + '-ms-transition': 'none', + '-ms-transform': 'translate(0px, 0px)', + '-o-transition': 'none', + '-o-transform': 'translate(0px, 0px)' + }); + tmp.resizing = false; + $(document).off('mousemove', tmp.mvMove); + $(document).off('mouseup', tmp.mvStop); + w2popup.unlock(); + } + return this; + }, + + keydown: function (event) { + var options = $('#w2ui-popup').data('options'); + if (!options.keyboard) return; + // trigger event + var eventData = w2popup.trigger({ phase: 'before', type: 'keydown', target: 'popup', options: options, originalEvent: event }); + if (eventData.isCancelled === true) return; + // default behavior + switch (event.keyCode) { + case 27: + event.preventDefault(); + if ($('#w2ui-popup .w2ui-popup-message').length > 0) w2popup.message(); else w2popup.close(); + break; + } + // event after + w2popup.trigger($.extend(eventData, { phase: 'after'})); + }, + + close: function (options) { + var obj = this; + var options = $.extend({}, $('#w2ui-popup').data('options'), options); + if ($('#w2ui-popup').length == 0) return; + // trigger event + var eventData = this.trigger({ phase: 'before', type: 'close', target: 'popup', options: options }); + if (eventData.isCancelled === true) return; + // default behavior + w2popup.status = 'closing'; + $('#w2ui-popup').css({ + '-webkit-transition': options.speed + 's opacity, ' + options.speed + 's -webkit-transform', + '-webkit-transform': 'scale(0.9)', + '-moz-transition': options.speed + 's opacity, ' + options.speed + 's -moz-transform', + '-moz-transform': 'scale(0.9)', + '-ms-transition': options.speed + 's opacity, ' + options.speed + 's -ms-transform', + '-ms-transform': 'scale(0.9)', + '-o-transition': options.speed + 's opacity, ' + options.speed + 's -o-transform', + '-o-transform': 'scale(0.9)', + 'opacity': '0' + }); + w2popup.unlockScreen(options); + setTimeout(function () { + $('#w2ui-popup').remove(); + w2popup.status = 'closed'; + // event after + obj.trigger($.extend(eventData, { phase: 'after'})); + }, options.speed * 1000); + // restore active + w2utils.keyboard.active(options._last_w2ui_name); + // remove keyboard events + if (options.keyboard) $(document).off('keydown', this.keydown); + }, + + toggle: function () { + var obj = this; + var options = $('#w2ui-popup').data('options'); + // trigger event + var eventData = this.trigger({ phase: 'before', type: 'toggle', target: 'popup', options: options }); + if (eventData.isCancelled === true) return; + // defatul action + if (options.maximized === true) w2popup.min(); else w2popup.max(); + // event after + setTimeout(function () { + obj.trigger($.extend(eventData, { phase: 'after'})); + }, (options.speed * 1000) + 50); + }, + + max: function () { + var obj = this; + var options = $('#w2ui-popup').data('options'); + if (options.maximized === true) return; + // trigger event + var eventData = this.trigger({ phase: 'before', type: 'max', target: 'popup', options: options }); + if (eventData.isCancelled === true) return; + // default behavior + w2popup.status = 'resizing'; + options.prevSize = $('#w2ui-popup').css('width') + ':' + $('#w2ui-popup').css('height'); + // do resize + w2popup.resize(10000, 10000, function () { + w2popup.status = 'open'; + options.maximized = true; + obj.trigger($.extend(eventData, { phase: 'after'})); + }); + }, + + min: function () { + var obj = this; + var options = $('#w2ui-popup').data('options'); + if (options.maximized !== true) return; + var size = options.prevSize.split(':'); + // trigger event + var eventData = this.trigger({ phase: 'before', type: 'min', target: 'popup', options: options }); + if (eventData.isCancelled === true) return; + // default behavior + w2popup.status = 'resizing'; + // do resize + w2popup.resize(size[0], size[1], function () { + w2popup.status = 'open'; + options.maximized = false; + options.prevSize = null; + obj.trigger($.extend(eventData, { phase: 'after'})); + }); + }, + + get: function () { + return $('#w2ui-popup').data('options'); + }, + + set: function (options) { + w2popup.open(options); + }, + + clear: function() { + $('#w2ui-popup .w2ui-msg-title').html(''); + $('#w2ui-popup .w2ui-msg-body').html(''); + $('#w2ui-popup .w2ui-msg-buttons').html(''); + }, + + reset: function () { + w2popup.open(w2popup.defaults); + }, + + load: function (options) { + w2popup.status = 'loading'; + if (String(options.url) == 'undefined') { + console.log('ERROR: The url parameter is empty.'); + return; + } + var tmp = String(options.url).split('#'); + var url = tmp[0]; + var selector = tmp[1]; + if (String(options) == 'undefined') options = {}; + // load url + var html = $('#w2ui-popup').data(url); + if (typeof html != 'undefined' && html != null) { + popup(html, selector); + } else { + $.get(url, function (data, status, obj) { // should always be $.get as it is template + popup(obj.responseText, selector); + $('#w2ui-popup').data(url, obj.responseText); // remember for possible future purposes + }); + } + function popup(html, selector) { + delete options.url; + $('body').append('<div id="w2ui-tmp" style="display: none">' + html + '</div>'); + if (typeof selector != 'undefined' && $('#w2ui-tmp #'+selector).length > 0) { + $('#w2ui-tmp #' + selector).w2popup(options); + } else { + $('#w2ui-tmp > div').w2popup(options); + } + // link styles + if ($('#w2ui-tmp > style').length > 0) { + var style = $('<div>').append($('#w2ui-tmp > style').clone()).html(); + if ($('#w2ui-popup #div-style').length == 0) { + $('#w2ui-popup').append('<div id="div-style" style="position: absolute; left: -100; width: 1px"></div>'); + } + $('#w2ui-popup #div-style').html(style); + } + $('#w2ui-tmp').remove(); + } + }, + + message: function (options) { + $().w2tag(); // hide all tags + if (!options) options = { width: 200, height: 100 }; + if (parseInt(options.width) < 10) options.width = 10; + if (parseInt(options.height) < 10) options.height = 10; + if (typeof options.hideOnClick == 'undefined') options.hideOnClick = false; + var poptions = $('#w2ui-popup').data('options') || {}; + if (typeof options.width == 'undefined' || options.width > poptions.width - 10) options.width = poptions.width - 10; + if (typeof options.height == 'undefined' || options.height > poptions.height - 40) options.height = poptions.height - 40; // title is 30px or so + + var head = $('#w2ui-popup .w2ui-msg-title'); + var pwidth = parseInt($('#w2ui-popup').width()); + var msgCount = $('#w2ui-popup .w2ui-popup-message').length; + // remove message + if ($.trim(options.html) == '') { + $('#w2ui-popup #w2ui-message'+ (msgCount-1)).css('z-Index', 250); + var options = $('#w2ui-popup #w2ui-message'+ (msgCount-1)).data('options') || {}; + $('#w2ui-popup #w2ui-message'+ (msgCount-1)).remove(); + if (typeof options.onClose == 'function') options.onClose(); + if (msgCount == 1) { + w2popup.unlock(); + } else { + $('#w2ui-popup #w2ui-message'+ (msgCount-2)).show(); + } + } else { + // hide previous messages + $('#w2ui-popup .w2ui-popup-message').hide(); + // add message + $('#w2ui-popup .w2ui-box1') + .before('<div id="w2ui-message' + msgCount + '" class="w2ui-popup-message" style="display: none; ' + + (head.length == 0 ? 'top: 0px;' : 'top: ' + w2utils.getSize(head, 'height') + 'px;') + + (typeof options.width != 'undefined' ? 'width: ' + options.width + 'px; left: ' + ((pwidth - options.width) / 2) + 'px;' : 'left: 10px; right: 10px;') + + (typeof options.height != 'undefined' ? 'height: ' + options.height + 'px;' : 'bottom: 6px;') + + '-webkit-transition: .3s; -moz-transition: .3s; -ms-transition: .3s; -o-transition: .3s;"' + + (options.hideOnClick === true ? 'onclick="w2popup.message();"' : '') + '>' + + '</div>'); + $('#w2ui-popup #w2ui-message'+ msgCount).data('options', options); + var display = $('#w2ui-popup #w2ui-message'+ msgCount).css('display'); + $('#w2ui-popup #w2ui-message'+ msgCount).css({ + '-webkit-transform': (display == 'none' ? 'translateY(-' + options.height + 'px)' : 'translateY(0px)'), + '-moz-transform': (display == 'none' ? 'translateY(-' + options.height + 'px)' : 'translateY(0px)'), + '-ms-transform': (display == 'none' ? 'translateY(-' + options.height + 'px)' : 'translateY(0px)'), + '-o-transform': (display == 'none' ? 'translateY(-' + options.height + 'px)' : 'translateY(0px)') + }); + if (display == 'none') { + $('#w2ui-popup #w2ui-message'+ msgCount).show().html(options.html); + // timer needs to animation + setTimeout(function () { + $('#w2ui-popup #w2ui-message'+ msgCount).css({ + '-webkit-transform': (display == 'none' ? 'translateY(0px)' : 'translateY(-' + options.height + 'px)'), + '-moz-transform': (display == 'none' ? 'translateY(0px)' : 'translateY(-' + options.height + 'px)'), + '-ms-transform': (display == 'none' ? 'translateY(0px)' : 'translateY(-' + options.height + 'px)'), + '-o-transform': (display == 'none' ? 'translateY(0px)' : 'translateY(-' + options.height + 'px)') + }); + }, 1); + // timer for lock + setTimeout(function() { + $('#w2ui-popup #w2ui-message'+ msgCount).css({ + '-webkit-transition': '0s', '-moz-transition': '0s', '-ms-transition': '0s', '-o-transition': '0s', + 'z-Index': 1500 + }); // has to be on top of lock + if (msgCount == 0) w2popup.lock(); + if (typeof options.onOpen == 'function') options.onOpen(); + }, 300); + } + } + }, + + lock: function (msg, showSpinner) { + var args = Array.prototype.slice.call(arguments, 0); + args.unshift($('#w2ui-popup')); + w2utils.lock.apply(window, args); + }, + + unlock: function () { + w2utils.unlock($('#w2ui-popup')); + }, + + // --- INTERNAL FUNCTIONS + + lockScreen: function (options) { + if ($('#w2ui-lock').length > 0) return false; + if (typeof options == 'undefined') options = $('#w2ui-popup').data('options'); + if (typeof options == 'undefined') options = {}; + options = $.extend({}, w2popup.defaults, options); + // show element + $('body').append('<div id="w2ui-lock" ' + + ' onmousewheel="if (event.stopPropagation) event.stopPropagation(); else event.cancelBubble = true; if (event.preventDefault) event.preventDefault(); else return false;"'+ + ' style="position: ' + (w2utils.engine == 'IE5' ? 'absolute' : 'fixed') + '; z-Index: 1199; left: 0px; top: 0px; ' + + ' padding: 0px; margin: 0px; background-color: ' + options.color + '; width: 100%; height: 100%; opacity: 0;"></div>'); + // lock screen + setTimeout(function () { + $('#w2ui-lock').css({ + '-webkit-transition': options.speed + 's opacity', + '-moz-transition': options.speed + 's opacity', + '-ms-transition': options.speed + 's opacity', + '-o-transition': options.speed + 's opacity', + 'opacity': options.opacity + }); + }, 1); + // add events + if (options.modal == true) { + $('#w2ui-lock').on('mousedown', function () { + $('#w2ui-lock').css({ + '-webkit-transition': '.1s', + '-moz-transition': '.1s', + '-ms-transition': '.1s', + '-o-transition': '.1s', + 'opacity': '0.6' + }); + // if (window.getSelection) window.getSelection().removeAllRanges(); + }); + $('#w2ui-lock').on('mouseup', function () { + setTimeout(function () { + $('#w2ui-lock').css({ + '-webkit-transition': '.1s', + '-moz-transition': '.1s', + '-ms-transition': '.1s', + '-o-transition': '.1s', + 'opacity': options.opacity + }); + }, 100); + // if (window.getSelection) window.getSelection().removeAllRanges(); + }); + } else { + $('#w2ui-lock').on('mouseup', function () { w2popup.close(); }); + } + return true; + }, + + unlockScreen: function (options) { + if ($('#w2ui-lock').length == 0) return false; + if (typeof options == 'undefined') options = $('#w2ui-popup').data('options'); + if (typeof options == 'undefined') options = {}; + options = $.extend({}, w2popup.defaults, options); + $('#w2ui-lock').css({ + '-webkit-transition': options.speed + 's opacity', + '-moz-transition': options.speed + 's opacity', + '-ms-transition': options.speed + 's opacity', + '-o-transition': options.speed + 's opacity', + 'opacity': 0 + }); + setTimeout(function () { + $('#w2ui-lock').remove(); + }, options.speed * 1000); + return true; + }, + + resize: function (width, height, callBack) { + var options = $('#w2ui-popup').data('options'); + // calculate new position + if (parseInt($(window).width()) - 10 < parseInt(width)) width = parseInt($(window).width()) - 10; + if (parseInt($(window).height()) - 10 < parseInt(height)) height = parseInt($(window).height()) - 10; + var top = ((parseInt($(window).height()) - parseInt(height)) / 2) * 0.8; + var left = (parseInt($(window).width()) - parseInt(width)) / 2; + // resize there + $('#w2ui-popup').css({ + '-webkit-transition': options.speed + 's width, ' + options.speed + 's height, ' + options.speed + 's left, ' + options.speed + 's top', + '-moz-transition': options.speed + 's width, ' + options.speed + 's height, ' + options.speed + 's left, ' + options.speed + 's top', + '-ms-transition': options.speed + 's width, ' + options.speed + 's height, ' + options.speed + 's left, ' + options.speed + 's top', + '-o-transition': options.speed + 's width, ' + options.speed + 's height, ' + options.speed + 's left, ' + options.speed + 's top', + 'top': top, + 'left': left, + 'width': width, + 'height': height + }); + setTimeout(function () { + options.width = width; + options.height = height; + if (typeof callBack == 'function') callBack(); + }, (options.speed * 1000) + 50); // give extra 50 ms + } + } + + // merge in event handling + $.extend(w2popup, w2utils.event); + +})(); + +// ============================================ +// --- Common dialogs + +var w2alert = function (msg, title, callBack) { + if (title == null) title = w2utils.lang('Notification'); + if ($('#w2ui-popup').length > 0 && w2popup.status != 'closing') { + w2popup.message({ + width : 400, + height : 170, + html : '<div style="position: absolute; top: 0px; left: 0px; right: 0px; bottom: 45px; overflow: auto">' + + ' <div class="w2ui-centered" style="font-size: 13px;">' + msg + '</div>' + + '</div>' + + '<div style="position: absolute; bottom: 7px; left: 0px; right: 0px; text-align: center; padding: 5px">' + + ' <button onclick="w2popup.message();" class="w2ui-popup-btn btn">' + w2utils.lang('Ok') + '</button>' + + '</div>', + onClose : function () { + if (typeof callBack == 'function') callBack(); + } + }); + } else { + w2popup.open({ + width : 450, + height : 220, + showMax : false, + showClose : false, + title : title, + body : '<div class="w2ui-centered" style="font-size: 13px;">' + msg + '</div>', + buttons : '<button onclick="w2popup.close();" class="w2ui-popup-btn btn">' + w2utils.lang('Ok') + '</button>', + onClose : function () { + if (typeof callBack == 'function') callBack(); + } + }); + } +}; + +var w2confirm = function (msg, title, callBack) { + var options = {}; + var defaults = { + msg : '', + title : w2utils.lang('Confirmation'), + width : ($('#w2ui-popup').length > 0 ? 400 : 450), + height : ($('#w2ui-popup').length > 0 ? 170 : 220), + yes_text : 'Yes', + yes_class : '', + yes_style : '', + yes_callBack: null, + no_text : 'No', + no_class : '', + no_style : '', + no_callBack : null, + callBack : null + }; + if (arguments.length == 1 && typeof msg == 'object') { + $.extend(options, defaults, msg); + } else { + if (typeof title == 'function') { + $.extend(options, defaults, { + msg : msg, + callBack: title + }) + } else { + $.extend(options, defaults, { + msg : msg, + title : title, + callBack: callBack + }) + } + } + if ($('#w2ui-popup').length > 0 && w2popup.status != 'closing') { + if (options.width > w2popup.get().width) options.width = w2popup.get().width; + if (options.height > (w2popup.get().height - 50)) options.height = w2popup.get().height - 50; + w2popup.message({ + width : options.width, + height : options.height, + html : '<div style="position: absolute; top: 0px; left: 0px; right: 0px; bottom: 40px; overflow: auto">' + + ' <div class="w2ui-centered" style="font-size: 13px;">' + options.msg + '</div>' + + '</div>' + + '<div style="position: absolute; bottom: 7px; left: 0px; right: 0px; text-align: center; padding: 5px">' + + ' <button id="Yes" class="w2ui-popup-btn btn '+ options.yes_class +'" style="'+ options.yes_style +'">' + w2utils.lang(options.yes_text) + '</button>' + + ' <button id="No" class="w2ui-popup-btn btn '+ options.no_class +'" style="'+ options.no_style +'">' + w2utils.lang(options.no_text) + '</button>' + + '</div>', + onOpen: function () { + $('#w2ui-popup .w2ui-popup-message .btn').on('click', function (event) { + w2popup.message(); + if (typeof options.callBack == 'function') options.callBack(event.target.id); + if (event.target.id == 'Yes' && typeof options.yes_callBack == 'function') options.yes_callBack(); + if (event.target.id == 'No' && typeof options.no_callBack == 'function') options.no_callBack(); + }); + }, + onKeydown: function (event) { + switch (event.originalEvent.keyCode) { + case 13: // enter + if (typeof options.callBack == 'function') options.callBack('Yes'); + if (typeof options.yes_callBack == 'function') options.yes_callBack(); + w2popup.message(); + break + case 27: // esc + if (typeof options.callBack == 'function') options.callBack('No'); + if (typeof options.no_callBack == 'function') options.no_callBack(); + w2popup.message(); + break + } + } + }); + + } else { + + if (!w2utils.isInt(options.height)) options.height = options.height + 50; + w2popup.open({ + width : options.width, + height : options.height, + title : options.title, + modal : true, + showClose : false, + body : '<div class="w2ui-centered" style="font-size: 13px;">' + options.msg + '</div>', + buttons : '<button id="Yes" class="w2ui-popup-btn btn '+ options.yes_class +'" style="'+ options.yes_style +'">'+ w2utils.lang(options.yes_text) +'</button>'+ + '<button id="No" class="w2ui-popup-btn btn '+ options.no_class +'" style="'+ options.no_style +'">'+ w2utils.lang(options.no_text) +'</button>', + onOpen: function (event) { + event.onComplete = function () { + $('#w2ui-popup .w2ui-popup-btn').on('click', function (event) { + w2popup.close(); + if (typeof options.callBack == 'function') options.callBack(event.target.id); + if (event.target.id == 'Yes' && typeof options.yes_callBack == 'function') options.yes_callBack(); + if (event.target.id == 'No' && typeof options.no_callBack == 'function') options.no_callBack(); + }); + } + }, + onKeydown: function (event) { + switch (event.originalEvent.keyCode) { + case 13: // enter + if (typeof options.callBack == 'function') options.callBack('Yes'); + if (typeof options.yes_callBack == 'function') options.yes_callBack(); + w2popup.close(); + break + case 27: // esc + if (typeof options.callBack == 'function') options.callBack('No'); + if (typeof options.no_callBack == 'function') options.no_callBack(); + w2popup.close(); + break + } + } + }); + } + + return { + yes: function (fun) { + options.yes_callBack = fun; + return this; + }, + no: function (fun) { + options.no_callBack = fun; + return this; + } + }; +}; +/************************************************************************ +* Library: Web 2.0 UI for jQuery (using prototypical inheritance) +* - Following objects defined +* - w2tabs - tabs widget +* - $().w2tabs - jQuery wrapper +* - Dependencies: jQuery, w2utils +* +* == NICE TO HAVE == +* - on overflow display << >> +* +************************************************************************/ + +(function () { + var w2tabs = function (options) { + this.box = null; // DOM Element that holds the element + this.name = null; // unique name for w2ui + this.active = null; + this.tabs = []; + this.routeData = {}; // data for dynamic routes + this.right = ''; + this.style = ''; + this.onClick = null; + this.onClose = null; + this.onRender = null; + this.onRefresh = null; + this.onResize = null; + this.onDestroy = null; + + $.extend(this, { handlers: [] }); + $.extend(true, this, w2obj.tabs, options); + }; + + // ==================================================== + // -- Registers as a jQuery plugin + + $.fn.w2tabs = function(method) { + if (typeof method === 'object' || !method ) { + // check name parameter + if (!w2utils.checkName(method, 'w2tabs')) return; + // extend tabs + var tabs = method.tabs || []; + var object = new w2tabs(method); + for (var i = 0; i < tabs.length; i++) { + object.tabs[i] = $.extend({}, w2tabs.prototype.tab, tabs[i]); + } + if ($(this).length !== 0) { + object.render($(this)[0]); + } + // register new object + w2ui[object.name] = object; + return object; + } else if (w2ui[$(this).attr('name')]) { + var obj = w2ui[$(this).attr('name')]; + obj[method].apply(obj, Array.prototype.slice.call(arguments, 1)); + return this; + } else { + console.log('ERROR: Method ' + method + ' does not exist on jQuery.w2tabs' ); + return undefined; + } + }; + + // ==================================================== + // -- Implementation of core functionality + + w2tabs.prototype = { + tab : { + id : null, // command to be sent to all event handlers + text : '', + route : null, + hidden : false, + disabled : false, + closable : false, + hint : '', + onClick : null, + onRefresh : null, + onClose : null + }, + + add: function (tab) { + return this.insert(null, tab); + }, + + insert: function (id, tab) { + if (!$.isArray(tab)) tab = [tab]; + // assume it is array + for (var i = 0; i < tab.length; i++) { + // checks + if (typeof tab[i].id === 'undefined') { + console.log('ERROR: The parameter "id" is required but not supplied. (obj: '+ this.name +')'); + return; + } + if (!w2utils.checkUniqueId(tab[i].id, this.tabs, 'tabs', this.name)) return; + // add tab + var newTab = $.extend({}, w2tabs.prototype.tab, tab[i]); + if (id === null || typeof id === 'undefined') { + this.tabs.push(newTab); + } else { + var middle = this.get(id, true); + this.tabs = this.tabs.slice(0, middle).concat([newTab], this.tabs.slice(middle)); + } + this.refresh(tab[i].id); + } + }, + + remove: function () { + var removed = 0; + for (var a = 0; a < arguments.length; a++) { + var tab = this.get(arguments[a]); + if (!tab) return false; + removed++; + // remove from array + this.tabs.splice(this.get(tab.id, true), 1); + // remove from screen + $(this.box).find('#tabs_'+ this.name +'_tab_'+ w2utils.escapeId(tab.id)).remove(); + } + return removed; + }, + + select: function (id) { + if (this.active == id || this.get(id) === null) return false; + this.active = id; + this.refresh(); + return true; + }, + + set: function (id, tab) { + var index = this.get(id, true); + if (index === null) return false; + $.extend(this.tabs[index], tab); + this.refresh(id); + return true; + }, + + get: function (id, returnIndex) { + if (arguments.length === 0) { + var all = []; + for (var i1 = 0; i1 < this.tabs.length; i1++) { + if (this.tabs[i1].id != null) { + all.push(this.tabs[i1].id); + } + } + return all; + } else { + for (var i2 = 0; i2 < this.tabs.length; i2++) { + if (this.tabs[i2].id == id) { // need to be == since id can be numeric + return (returnIndex === true ? i2 : this.tabs[i2]); + } + } + } + return null; + }, + + show: function () { + var obj = this; + var shown = 0; + var tmp = []; + for (var a = 0; a < arguments.length; a++) { + var tab = this.get(arguments[a]); + if (!tab || tab.hidden === false) continue; + shown++; + tab.hidden = false; + tmp.push(tab.id); + } + setTimeout(function () { for (var t in tmp) obj.refresh(tmp[t]); }, 15); // needs timeout + return shown; + }, + + hide: function () { + var obj = this; + var hidden= 0; + var tmp = []; + for (var a = 0; a < arguments.length; a++) { + var tab = this.get(arguments[a]); + if (!tab || tab.hidden === true) continue; + hidden++; + tab.hidden = true; + tmp.push(tab.id); + } + setTimeout(function () { for (var t in tmp) obj.refresh(tmp[t]); }, 15); // needs timeout + return hidden; + }, + + enable: function () { + var obj = this; + var enabled = 0; + var tmp = []; + for (var a = 0; a < arguments.length; a++) { + var tab = this.get(arguments[a]); + if (!tab || tab.disabled === false) continue; + enabled++; + tab.disabled = false; + tmp.push(tab.id); + } + setTimeout(function () { for (var t in tmp) obj.refresh(tmp[t]); }, 15); // needs timeout + return enabled; + }, + + disable: function () { + var obj = this; + var disabled = 0; + var tmp = []; + for (var a = 0; a < arguments.length; a++) { + var tab = this.get(arguments[a]); + if (!tab || tab.disabled === true) continue; + disabled++; + tab.disabled = true; + tmp.push(tab.id); + } + setTimeout(function () { for (var t in tmp) obj.refresh(tmp[t]); }, 15); // needs timeout + return disabled; + }, + + refresh: function (id) { + var time = (new Date()).getTime(); + // if (window.getSelection) window.getSelection().removeAllRanges(); // clear selection + // event before + var eventData = this.trigger({ phase: 'before', type: 'refresh', target: (typeof id !== 'undefined' ? id : this.name), object: this.get(id) }); + if (eventData.isCancelled === true) return; + if (typeof id === 'undefined') { + // refresh all + for (var i = 0; i < this.tabs.length; i++) this.refresh(this.tabs[i].id); + } else { + // create or refresh only one item + var tab = this.get(id); + if (tab === null) return false; + if (typeof tab.caption !== 'undefined') tab.text = tab.caption; + + var jq_el = $(this.box).find('#tabs_'+ this.name +'_tab_'+ w2utils.escapeId(tab.id)); + var tabHTML = (tab.closable ? '<div class="w2ui-tab-close" onclick="w2ui[\''+ this.name +'\'].animateClose(\''+ tab.id +'\', event);"></div>' : '') + + ' <div class="w2ui-tab'+ (this.active === tab.id ? ' active' : '') + (tab.closable ? ' closable' : '') +'" '+ + ' title="'+ (typeof tab.hint !== 'undefined' ? tab.hint : '') +'"'+ + ' onclick="w2ui[\''+ this.name +'\'].click(\''+ tab.id +'\', event);">' + tab.text + '</div>'; + if (jq_el.length === 0) { + // does not exist - create it + var addStyle = ''; + if (tab.hidden) { addStyle += 'display: none;'; } + if (tab.disabled) { addStyle += 'opacity: 0.2; -moz-opacity: 0.2; -webkit-opacity: 0.2; -o-opacity: 0.2; filter:alpha(opacity=20);'; } + var html = '<td id="tabs_'+ this.name + '_tab_'+ tab.id +'" style="'+ addStyle +'" valign="middle">'+ tabHTML + '</td>'; + if (this.get(id, true) !== this.tabs.length-1 && $(this.box).find('#tabs_'+ this.name +'_tab_'+ w2utils.escapeId(this.tabs[parseInt(this.get(id, true))+1].id)).length > 0) { + $(this.box).find('#tabs_'+ this.name +'_tab_'+ w2utils.escapeId(this.tabs[parseInt(this.get(id, true))+1].id)).before(html); + } else { + $(this.box).find('#tabs_'+ this.name +'_right').before(html); + } + } else { + // refresh + jq_el.html(tabHTML); + if (tab.hidden) { jq_el.css('display', 'none'); } + else { jq_el.css('display', ''); } + if (tab.disabled) { jq_el.css({ 'opacity': '0.2', '-moz-opacity': '0.2', '-webkit-opacity': '0.2', '-o-opacity': '0.2', 'filter': 'alpha(opacity=20)' }); } + else { jq_el.css({ 'opacity': '1', '-moz-opacity': '1', '-webkit-opacity': '1', '-o-opacity': '1', 'filter': 'alpha(opacity=100)' }); } + } + } + // right html + $('#tabs_'+ this.name +'_right').html(this.right); + // event after + this.trigger($.extend(eventData, { phase: 'after' })); + return (new Date()).getTime() - time; + }, + + render: function (box) { + var time = (new Date()).getTime(); + // event before + var eventData = this.trigger({ phase: 'before', type: 'render', target: this.name, box: box }); + if (eventData.isCancelled === true) return; + // default action + // if (window.getSelection) window.getSelection().removeAllRanges(); // clear selection + if (typeof box !== 'undefined' && box !== null) { + if ($(this.box).find('> table #tabs_'+ this.name + '_right').length > 0) { + $(this.box) + .removeAttr('name') + .removeClass('w2ui-reset w2ui-tabs') + .html(''); + } + this.box = box; + } + if (!this.box) return false; + // render all buttons + var html = '<table cellspacing="0" cellpadding="1" width="100%">'+ + ' <tr><td width="100%" id="tabs_'+ this.name +'_right" align="right">'+ this.right +'</td></tr>'+ + '</table>'; + $(this.box) + .attr('name', this.name) + .addClass('w2ui-reset w2ui-tabs') + .html(html); + if ($(this.box).length > 0) $(this.box)[0].style.cssText += this.style; + // event after + this.trigger($.extend(eventData, { phase: 'after' })); + this.refresh(); + return (new Date()).getTime() - time; + }, + + resize: function () { + var time = (new Date()).getTime(); + // event before + var eventData = this.trigger({ phase: 'before', type: 'resize', target: this.name }); + if (eventData.isCancelled === true) return; + + // intentionaly blank + + // event after + this.trigger($.extend(eventData, { phase: 'after' })); + return (new Date()).getTime() - time; + }, + + destroy: function () { + // event before + var eventData = this.trigger({ phase: 'before', type: 'destroy', target: this.name }); + if (eventData.isCancelled === true) return; + // clean up + if ($(this.box).find('> table #tabs_'+ this.name + '_right').length > 0) { + $(this.box) + .removeAttr('name') + .removeClass('w2ui-reset w2ui-tabs') + .html(''); + } + delete w2ui[this.name]; + // event after + this.trigger($.extend(eventData, { phase: 'after' })); + }, + + // =================================================== + // -- Internal Event Handlers + + click: function (id, event) { + var tab = this.get(id); + if (tab === null || tab.disabled) return false; + // event before + var eventData = this.trigger({ phase: 'before', type: 'click', target: id, tab: tab, object: tab, originalEvent: event }); + if (eventData.isCancelled === true) return; + // default action + $(this.box).find('#tabs_'+ this.name +'_tab_'+ w2utils.escapeId(this.active) +' .w2ui-tab').removeClass('active'); + this.active = tab.id; + // route processing + if (tab.route) { + var route = String('/'+ tab.route).replace(/\/{2,}/g, '/'); + var info = w2utils.parseRoute(route); + if (info.keys.length > 0) { + for (var k = 0; k < info.keys.length; k++) { + if (this.routeData[info.keys[k].name] == null) continue; + route = route.replace((new RegExp(':'+ info.keys[k].name, 'g')), this.routeData[info.keys[k].name]); + } + } + setTimeout(function () { window.location.hash = route; }, 1); + } + // event after + this.trigger($.extend(eventData, { phase: 'after' })); + this.refresh(id); + }, + + animateClose: function(id, event) { + var tab = this.get(id); + if (tab === null || tab.disabled) return false; + // event before + var eventData = this.trigger({ phase: 'before', type: 'close', target: id, object: this.get(id), originalEvent: event }); + if (eventData.isCancelled === true) return; + // default action + var obj = this; + $(this.box).find('#tabs_'+ this.name +'_tab_'+ w2utils.escapeId(tab.id)).css({ + '-webkit-transition': '.2s', + '-moz-transition': '2s', + '-ms-transition': '.2s', + '-o-transition': '.2s', + opacity: '0' }); + setTimeout(function () { + var width = $(obj.box).find('#tabs_'+ obj.name +'_tab_'+ w2utils.escapeId(tab.id)).width(); + $(obj.box).find('#tabs_'+ obj.name +'_tab_'+ w2utils.escapeId(tab.id)) + .html('<div style="width: '+ width +'px; -webkit-transition: .2s; -moz-transition: .2s; -ms-transition: .2s; -o-transition: .2s"></div>'); + setTimeout(function () { + $(obj.box).find('#tabs_'+ obj.name +'_tab_'+ w2utils.escapeId(tab.id)).find(':first-child').css({ 'width': '0px' }); + }, 50); + }, 200); + setTimeout(function () { + obj.remove(id); + }, 450); + // event before + this.trigger($.extend(eventData, { phase: 'after' })); + this.refresh(); + }, + + animateInsert: function(id, tab) { + if (this.get(id) === null) return; + if (!$.isPlainObject(tab)) return; + // check for unique + if (!w2utils.checkUniqueId(tab.id, this.tabs, 'tabs', this.name)) return; + // insert simple div + var jq_el = $(this.box).find('#tabs_'+ this.name +'_tab_'+ w2utils.escapeId(tab.id)); + if (jq_el.length !== 0) return; // already exists + // measure width + if (typeof tab.caption !== 'undefined') tab.text = tab.caption; + var tmp = '<div id="_tmp_tabs" class="w2ui-reset w2ui-tabs" style="position: absolute; top: -1000px;">'+ + '<table cellspacing="0" cellpadding="1" width="100%"><tr>'+ + '<td id="_tmp_simple_tab" style="" valign="middle">'+ + (tab.closable ? '<div class="w2ui-tab-close"></div>' : '') + + ' <div class="w2ui-tab '+ (this.active === tab.id ? 'active' : '') +'">'+ tab.text +'</div>'+ + '</td></tr></table>'+ + '</div>'; + $('body').append(tmp); + // create dummy element + var tabHTML = '<div style="width: 1px; -webkit-transition: 0.2s; -moz-transition: 0.2s; -ms-transition: 0.2s; -o-transition: 0.2s;"> </div>'; + var addStyle = ''; + if (tab.hidden) { addStyle += 'display: none;'; } + if (tab.disabled) { addStyle += 'opacity: 0.2; -moz-opacity: 0.2; -webkit-opacity: 0.2; -o-opacity: 0.2; filter:alpha(opacity=20);'; } + var html = '<td id="tabs_'+ this.name +'_tab_'+ tab.id +'" style="'+ addStyle +'" valign="middle">'+ tabHTML +'</td>'; + if (this.get(id, true) !== this.tabs.length && $(this.box).find('#tabs_'+ this.name +'_tab_'+ w2utils.escapeId(this.tabs[parseInt(this.get(id, true))].id)).length > 0) { + $(this.box).find('#tabs_'+ this.name +'_tab_'+ w2utils.escapeId(this.tabs[parseInt(this.get(id, true))].id)).before(html); + } else { + $(this.box).find('#tabs_'+ this.name +'_right').before(html); + } + // -- move + var obj = this; + setTimeout(function () { + var width = $('#_tmp_simple_tab').width(); + $('#_tmp_tabs').remove(); + $('#tabs_'+ obj.name +'_tab_'+ w2utils.escapeId(tab.id) +' > div').css('width', width+'px'); + }, 1); + setTimeout(function () { + // insert for real + obj.insert(id, tab); + }, 200); + } + }; + + $.extend(w2tabs.prototype, w2utils.event); + w2obj.tabs = w2tabs; +})(); + + +/************************************************************************ +* Library: Web 2.0 UI for jQuery (using prototypical inheritance) +* - Following objects defined +* - w2toolbar - toolbar widget +* - $().w2toolbar - jQuery wrapper +* - Dependencies: jQuery, w2utils +* +* == NICE TO HAVE == +* - on overflow display << >> +* - verticle toolbar +* +************************************************************************/ + +(function () { + var w2toolbar = function (options) { + this.box = null; // DOM Element that holds the element + this.name = null; // unique name for w2ui + this.routeData = {}; // data for dynamic routes + this.items = []; + this.right = ''; // HTML text on the right of toolbar + this.onClick = null; + this.onRender = null; + this.onRefresh = null; + this.onResize = null; + this.onDestroy = null; + + $.extend(true, this, w2obj.toolbar, options); + }; + + // ==================================================== + // -- Registers as a jQuery plugin + + $.fn.w2toolbar = function(method) { + if (typeof method === 'object' || !method ) { + // check name parameter + if (!w2utils.checkName(method, 'w2toolbar')) return; + // extend items + var items = method.items || []; + var object = new w2toolbar(method); + $.extend(object, { items: [], handlers: [] }); + for (var i = 0; i < items.length; i++) { + object.items[i] = $.extend({}, w2toolbar.prototype.item, items[i]); + } + if ($(this).length !== 0) { + object.render($(this)[0]); + } + // register new object + w2ui[object.name] = object; + return object; + + } else if (w2ui[$(this).attr('name')]) { + var obj = w2ui[$(this).attr('name')]; + obj[method].apply(obj, Array.prototype.slice.call(arguments, 1)); + return this; + } else { + console.log('ERROR: Method ' + method + ' does not exist on jQuery.w2toolbar' ); + } + }; + + // ==================================================== + // -- Implementation of core functionality + + w2toolbar.prototype = { + item: { + id : null, // command to be sent to all event handlers + type : 'button', // button, check, radio, drop, menu, break, html, spacer + text : '', + route : null, // if not null, it is route to go + html : '', + img : null, + icon : null, + count : null, + hidden : false, + disabled : false, + checked : false, // used for radio buttons + arrow : true, // arrow down for drop/menu types + hint : '', + group : null, // used for radio buttons + items : null, // for type menu it is an array of items in the menu + overlay : {}, + onClick : null + }, + + add: function (items) { + this.insert(null, items); + }, + + insert: function (id, items) { + if (!$.isArray(items)) items = [items]; + for (var o = 0; o < items.length; o++) { + // checks + if (typeof items[o].type === 'undefined') { + console.log('ERROR: The parameter "type" is required but not supplied in w2toolbar.add() method.'); + return; + } + if ($.inArray(String(items[o].type), ['button', 'check', 'radio', 'drop', 'menu', 'break', 'html', 'spacer']) === -1) { + console.log('ERROR: The parameter "type" should be one of the following [button, check, radio, drop, menu, break, html, spacer] '+ + 'in w2toolbar.add() method.'); + return; + } + if (typeof items[o].id === 'undefined') { + console.log('ERROR: The parameter "id" is required but not supplied in w2toolbar.add() method.'); + return; + } + if (!w2utils.checkUniqueId(items[o].id, this.items, 'toolbar items', this.name)) return; + // add item + var it = $.extend({}, w2toolbar.prototype.item, items[o]); + if (id == null) { + this.items.push(it); + } else { + var middle = this.get(id, true); + this.items = this.items.slice(0, middle).concat([it], this.items.slice(middle)); + } + this.refresh(it.id); + } + }, + + remove: function () { + var removed = 0; + for (var a = 0; a < arguments.length; a++) { + var it = this.get(arguments[a]); + if (!it) continue; + removed++; + // remove from screen + $(this.box).find('#tb_'+ this.name +'_item_'+ w2utils.escapeId(it.id)).remove(); + // remove from array + var ind = this.get(it.id, true); + if (ind) this.items.splice(ind, 1); + } + return removed; + }, + + set: function (id, item) { + var index = this.get(id, true); + if (index === null) return false; + $.extend(this.items[index], item); + this.refresh(id); + return true; + }, + + get: function (id, returnIndex) { + if (arguments.length === 0) { + var all = []; + for (var i1 = 0; i1 < this.items.length; i1++) if (this.items[i1].id !== null) all.push(this.items[i1].id); + return all; + } + for (var i2 = 0; i2 < this.items.length; i2++) { + if (this.items[i2].id === id) { + if (returnIndex === true) return i2; else return this.items[i2]; + } + } + return null; + }, + + show: function () { + var obj = this; + var items = 0; + var tmp = []; + for (var a = 0; a < arguments.length; a++) { + var it = this.get(arguments[a]); + if (!it) continue; + items++; + it.hidden = false; + tmp.push(it.id); + } + setTimeout(function () { for (var t in tmp) obj.refresh(tmp[t]); }, 15); // needs timeout + return items; + }, + + hide: function () { + var obj = this; + var items = 0; + var tmp = []; + for (var a = 0; a < arguments.length; a++) { + var it = this.get(arguments[a]); + if (!it) continue; + items++; + it.hidden = true; + tmp.push(it.id); + } + setTimeout(function () { for (var t in tmp) obj.refresh(tmp[t]); }, 15); // needs timeout + return items; + }, + + enable: function () { + var obj = this; + var items = 0; + var tmp = []; + for (var a = 0; a < arguments.length; a++) { + var it = this.get(arguments[a]); + if (!it) continue; + items++; + it.disabled = false; + tmp.push(it.id); + } + setTimeout(function () { for (var t in tmp) obj.refresh(tmp[t]); }, 15); // needs timeout + return items; + }, + + disable: function () { + var obj = this; + var items = 0; + var tmp = []; + for (var a = 0; a < arguments.length; a++) { + var it = this.get(arguments[a]); + if (!it) continue; + items++; + it.disabled = true; + tmp.push(it.id); + } + setTimeout(function () { for (var t in tmp) obj.refresh(tmp[t]); }, 15); // needs timeout + return items; + }, + + check: function () { + var obj = this; + var items = 0; + var tmp = []; + for (var a = 0; a < arguments.length; a++) { + var it = this.get(arguments[a]); + if (!it) continue; + items++; + it.checked = true; + tmp.push(it.id); + } + setTimeout(function () { for (var t in tmp) obj.refresh(tmp[t]); }, 15); // needs timeout + return items; + }, + + uncheck: function () { + var obj = this; + var items = 0; + var tmp = []; + for (var a = 0; a < arguments.length; a++) { + var it = this.get(arguments[a]); + if (!it) continue; + items++; + it.checked = false; + tmp.push(it.id); + } + setTimeout(function () { for (var t in tmp) obj.refresh(tmp[t]); }, 15); // needs timeout + return items; + }, + + render: function (box) { + var time = (new Date()).getTime(); + // event before + var eventData = this.trigger({ phase: 'before', type: 'render', target: this.name, box: box }); + if (eventData.isCancelled === true) return; + + if (box != null) { + if ($(this.box).find('> table #tb_'+ this.name + '_right').length > 0) { + $(this.box) + .removeAttr('name') + .removeClass('w2ui-reset w2ui-toolbar') + .html(''); + } + this.box = box; + } + if (!this.box) return; + // render all buttons + var html = '<table cellspacing="0" cellpadding="0" width="100%">'+ + '<tr>'; + for (var i = 0; i < this.items.length; i++) { + var it = this.items[i]; + if (it.id == null) it.id = "item_" + i; + if (it === null) continue; + if (it.type === 'spacer') { + html += '<td width="100%" id="tb_'+ this.name +'_item_'+ it.id +'" align="right"></td>'; + } else { + html += '<td id="tb_'+ this.name + '_item_'+ it.id +'" style="'+ (it.hidden ? 'display: none' : '') +'" '+ + ' class="'+ (it.disabled ? 'disabled' : '') +'" valign="middle">'+ this.getItemHTML(it) + + '</td>'; + } + } + html += '<td width="100%" id="tb_'+ this.name +'_right" align="right">'+ this.right +'</td>'; + html += '</tr>'+ + '</table>'; + $(this.box) + .attr('name', this.name) + .addClass('w2ui-reset w2ui-toolbar') + .html(html); + if ($(this.box).length > 0) $(this.box)[0].style.cssText += this.style; + // event after + this.trigger($.extend(eventData, { phase: 'after' })); + return (new Date()).getTime() - time; + }, + + refresh: function (id) { + var time = (new Date()).getTime(); + // event before + var eventData = this.trigger({ phase: 'before', type: 'refresh', target: (typeof id !== 'undefined' ? id : this.name), item: this.get(id) }); + if (eventData.isCancelled === true) return; + + if (id == null) { + // refresh all + for (var i = 0; i < this.items.length; i++) { + var it1 = this.items[i]; + if (it1.id == null) it1.id = "item_" + i; + this.refresh(it1.id); + } + } + // create or refresh only one item + var it = this.get(id); + if (it === null) return false; + + var el = $(this.box).find('#tb_'+ this.name +'_item_'+ w2utils.escapeId(it.id)); + var html = this.getItemHTML(it); + if (el.length === 0) { + // does not exist - create it + if (it.type === 'spacer') { + html = '<td width="100%" id="tb_'+ this.name +'_item_'+ it.id +'" align="right"></td>'; + } else { + html = '<td id="tb_'+ this.name + '_item_'+ it.id +'" style="'+ (it.hidden ? 'display: none' : '') +'" '+ + ' class="'+ (it.disabled ? 'disabled' : '') +'" valign="middle">'+ html + + '</td>'; + } + if (this.get(id, true) === this.items.length-1) { + $(this.box).find('#tb_'+ this.name +'_right').before(html); + } else { + $(this.box).find('#tb_'+ this.name +'_item_'+ w2utils.escapeId(this.items[parseInt(this.get(id, true))+1].id)).before(html); + } + } else { + // refresh + el.html(html); + if (it.hidden) { el.css('display', 'none'); } else { el.css('display', ''); } + if (it.disabled) { el.addClass('disabled'); } else { el.removeClass('disabled'); } + } + // event after + this.trigger($.extend(eventData, { phase: 'after' })); + return (new Date()).getTime() - time; + }, + + resize: function () { + var time = (new Date()).getTime(); + // event before + var eventData = this.trigger({ phase: 'before', type: 'resize', target: this.name }); + if (eventData.isCancelled === true) return; + + // intentionaly blank + + // event after + this.trigger($.extend(eventData, { phase: 'after' })); + return (new Date()).getTime() - time; + }, + + destroy: function () { + // event before + var eventData = this.trigger({ phase: 'before', type: 'destroy', target: this.name }); + if (eventData.isCancelled === true) return; + // clean up + if ($(this.box).find('> table #tb_'+ this.name + '_right').length > 0) { + $(this.box) + .removeAttr('name') + .removeClass('w2ui-reset w2ui-toolbar') + .html(''); + } + $(this.box).html(''); + delete w2ui[this.name]; + // event after + this.trigger($.extend(eventData, { phase: 'after' })); + }, + + // ======================================== + // --- Internal Functions + + getItemHTML: function (item) { + var html = ''; + + if (typeof item.caption !== 'undefined') item.text = item.caption; + if (typeof item.hint === 'undefined') item.hint = ''; + if (typeof item.text === 'undefined') item.text = ''; + + switch (item.type) { + case 'menu': + case 'button': + case 'check': + case 'radio': + case 'drop': + var img = '<td> </td>'; + if (item.img) img = '<td><div class="w2ui-tb-image w2ui-icon '+ item.img +'"></div></td>'; + if (item.icon) img = '<td><div class="w2ui-tb-image"><span class="'+ item.icon +'"></span></div></td>'; + html += '<table cellpadding="0" cellspacing="0" title="'+ item.hint +'" class="w2ui-button '+ (item.checked ? 'checked' : '') +'" '+ + ' onclick = "var el=w2ui[\''+ this.name + '\']; if (el) el.click(\''+ item.id +'\', event);" '+ + ' onmouseover = "' + (!item.disabled ? "$(this).addClass('over');" : "") + '"'+ + ' onmouseout = "' + (!item.disabled ? "$(this).removeClass('over').removeClass('down');" : "") + '"'+ + ' onmousedown = "' + (!item.disabled ? "$(this).addClass('down');" : "") + '"'+ + ' onmouseup = "' + (!item.disabled ? "$(this).removeClass('down');" : "") + '"'+ + '>'+ + '<tr><td>'+ + ' <table cellpadding="1" cellspacing="0">'+ + ' <tr>' + + img + + (item.text !== '' ? '<td class="w2ui-tb-caption" nowrap>'+ item.text +'</td>' : '') + + (item.count != null ? '<td class="w2ui-tb-count" nowrap><span>'+ item.count +'</span></td>' : '') + + (((item.type === 'drop' || item.type === 'menu') && item.arrow !== false) ? + '<td class="w2ui-tb-down" nowrap><div></div></td>' : '') + + ' </tr></table>'+ + '</td></tr></table>'; + break; + + case 'break': + html += '<table cellpadding="0" cellspacing="0"><tr>'+ + ' <td><div class="w2ui-break"> </div></td>'+ + '</tr></table>'; + break; + + case 'html': + html += '<table cellpadding="0" cellspacing="0"><tr>'+ + ' <td nowrap>' + item.html + '</td>'+ + '</tr></table>'; + break; + } + + var newHTML = ''; + if (typeof item.onRender === 'function') newHTML = item.onRender.call(this, item.id, html); + if (typeof this.onRender === 'function') newHTML = this.onRender(item.id, html); + if (newHTML !== '' && newHTML != null) html = newHTML; + return html; + }, + + menuClick: function (event) { + var obj = this; + if (event.item && !event.item.disabled) { + // event before + var eventData = this.trigger({ phase: 'before', type: 'click', target: event.item.id + ':' + event.subItem.id, item: event.item, + subItem: event.subItem, originalEvent: event.originalEvent }); + if (eventData.isCancelled === true) return; + + // route processing + var it = event.subItem; + if (it.route) { + var route = String('/'+ it.route).replace(/\/{2,}/g, '/'); + var info = w2utils.parseRoute(route); + if (info.keys.length > 0) { + for (var k = 0; k < info.keys.length; k++) { + if (obj.routeData[info.keys[k].name] == null) continue; + route = route.replace((new RegExp(':'+ info.keys[k].name, 'g')), this.routeData[info.keys[k].name]); + } + } + setTimeout(function () { window.location.hash = route; }, 1); + } + + // event after + this.trigger($.extend(eventData, { phase: 'after' })); + } + }, + + click: function (id, event) { + var obj = this; + var it = this.get(id); + if (it && !it.disabled) { + // event before + var eventData = this.trigger({ phase: 'before', type: 'click', target: (typeof id !== 'undefined' ? id : this.name), + item: it, object: it, originalEvent: event }); + if (eventData.isCancelled === true) return; + + var btn = $('#tb_'+ this.name +'_item_'+ w2utils.escapeId(it.id) +' table.w2ui-button'); + btn.removeClass('down'); + + if (it.type === 'radio') { + for (var i = 0; i < this.items.length; i++) { + var itt = this.items[i]; + if (itt == null || itt.id === it.id || itt.type !== 'radio') continue; + if (itt.group === it.group && itt.checked) { + itt.checked = false; + this.refresh(itt.id); + } + } + it.checked = true; + btn.addClass('checked'); + } + + if (it.type === 'drop' || it.type === 'menu') { + if (it.checked) { + // if it was already checked, second click will hide it + it.checked = false; + } else { + // show overlay + setTimeout(function () { + var el = $('#tb_'+ obj.name +'_item_'+ w2utils.escapeId(it.id)); + if (!$.isPlainObject(it.overlay)) it.overlay = {}; + var left = (el.width() - 50) / 2; + if (left > 19) left = 19; + if (it.type === 'drop') { + el.w2overlay(it.html, $.extend({ left: left, top: 3 }, it.overlay)); + } + if (it.type === 'menu') { + el.w2menu(it.items, $.extend({ left: left, top: 3 }, it.overlay, { + select: function (event) { + obj.menuClick({ item: it, subItem: event.item, originalEvent: event.originalEvent }); + hideDrop(); + } + })); + } + // window.click to hide it + $(document).on('click', hideDrop); + function hideDrop() { + $(document).off('click', hideDrop); + it.checked = false; + btn.removeClass('checked'); + } + }, 1); + } + } + + if (it.type === 'check' || it.type === 'drop' || it.type === 'menu') { + it.checked = !it.checked; + if (it.checked) { + btn.addClass('checked'); + } else { + btn.removeClass('checked'); + } + } + // route processing + if (it.route) { + var route = String('/'+ it.route).replace(/\/{2,}/g, '/'); + var info = w2utils.parseRoute(route); + if (info.keys.length > 0) { + for (var k = 0; k < info.keys.length; k++) { + route = route.replace((new RegExp(':'+ info.keys[k].name, 'g')), this.routeData[info.keys[k].name]); + } + } + setTimeout(function () { window.location.hash = route; }, 1); + } + // event after + this.trigger($.extend(eventData, { phase: 'after' })); + } + } + }; + + $.extend(w2toolbar.prototype, w2utils.event); + w2obj.toolbar = w2toolbar; +})(); + + +/************************************************************************ +* Library: Web 2.0 UI for jQuery (using prototypical inheritance) +* - Following objects defined +* - w2sidebar - sidebar widget +* - $().w2sidebar - jQuery wrapper +* - Dependencies: jQuery, w2utils +* +* == NICE TO HAVE == +* - return ids of all subitems +* - add find() method to find nodes by a specific criteria (I want all nodes for exampe) +* - dbl click should be like it is in grid (with timer not HTML dbl click event) +* - reorder with grag and drop +* - add route property that would navigate to a #route +* - node.style is missleading - should be there to apply color for example +* +************************************************************************/ + +(function () { + var w2sidebar = function (options) { + this.name = null; + this.box = null; + this.sidebar = null; + this.parent = null; + this.nodes = []; // Sidebar child nodes + this.menu = []; + this.routeData = {}; // data for dynamic routes + this.selected = null; // current selected node (readonly) + this.img = null; + this.icon = null; + this.style = ''; + this.topHTML = ''; + this.bottomHTML = ''; + this.keyboard = true; + this.onClick = null; // Fire when user click on Node Text + this.onDblClick = null; // Fire when user dbl clicks + this.onContextMenu = null; + this.onMenuClick = null; // when context menu item selected + this.onExpand = null; // Fire when node Expands + this.onCollapse = null; // Fire when node Colapses + this.onKeydown = null; + this.onRender = null; + this.onRefresh = null; + this.onResize = null; + this.onDestroy = null; + + $.extend(true, this, w2obj.sidebar, options); + }; + + // ==================================================== + // -- Registers as a jQuery plugin + + $.fn.w2sidebar = function(method) { + if (typeof method === 'object' || !method ) { + // check name parameter + if (!w2utils.checkName(method, 'w2sidebar')) return; + // extend items + var nodes = method.nodes; + var object = new w2sidebar(method); + $.extend(object, { handlers: [], nodes: [] }); + if (typeof nodes != 'undefined') { + object.add(object, nodes); + } + if ($(this).length !== 0) { + object.render($(this)[0]); + } + object.sidebar = object; + // register new object + w2ui[object.name] = object; + return object; + + } else if (w2ui[$(this).attr('name')]) { + var obj = w2ui[$(this).attr('name')]; + obj[method].apply(obj, Array.prototype.slice.call(arguments, 1)); + return this; + } else { + console.log('ERROR: Method ' + method + ' does not exist on jQuery.w2sidebar' ); + } + }; + + // ==================================================== + // -- Implementation of core functionality + + w2sidebar.prototype = { + + node: { + id : null, + text : '', + count : null, + img : null, + icon : null, + nodes : [], + style : '', // additional style for subitems + route : null, + selected : false, + expanded : false, + hidden : false, + disabled : false, + group : false, // if true, it will build as a group + groupShowHide : true, + plus : false, // if true, plus will be shown even if there is no sub nodes + // events + onClick : null, + onDblClick : null, + onContextMenu : null, + onExpand : null, + onCollapse : null, + // internal + parent : null, // node object + sidebar : null + }, + + add: function (parent, nodes) { + if (arguments.length == 1) { + // need to be in reverse order + nodes = arguments[0]; + parent = this; + } + if (typeof parent == 'string') parent = this.get(parent); + return this.insert(parent, null, nodes); + }, + + insert: function (parent, before, nodes) { + var txt, ind, tmp, node, nd; + if (arguments.length == 2) { + // need to be in reverse order + nodes = arguments[1]; + before = arguments[0]; + ind = this.get(before); + if (ind === null) { + if (!$.isArray(nodes)) nodes = [nodes]; + txt = (nodes[0].caption != null ? nodes[0].caption : nodes[0].text); + console.log('ERROR: Cannot insert node "'+ txt +'" because cannot find node "'+ before +'" to insert before.'); + return null; + } + parent = this.get(before).parent; + } + if (typeof parent == 'string') parent = this.get(parent); + if (!$.isArray(nodes)) nodes = [nodes]; + for (var o in nodes) { + node = nodes[o]; + if (typeof node.id == null) { + txt = (node.caption != null ? node.caption : node.text); + console.log('ERROR: Cannot insert node "'+ txt +'" because it has no id.'); + continue; + } + if (this.get(this, node.id) !== null) { + txt = (node.caption != null ? node.caption : node.text); + console.log('ERROR: Cannot insert node with id='+ node.id +' (text: '+ txt + ') because another node with the same id already exists.'); + continue; + } + tmp = $.extend({}, w2sidebar.prototype.node, node); + tmp.sidebar = this; + tmp.parent = parent; + nd = tmp.nodes || []; + tmp.nodes = []; // very important to re-init empty nodes array + if (before === null) { // append to the end + parent.nodes.push(tmp); + } else { + ind = this.get(parent, before, true); + if (ind === null) { + txt = (node.caption != null ? node.caption : node.text); + console.log('ERROR: Cannot insert node "'+ txt +'" because cannot find node "'+ before +'" to insert before.'); + return null; + } + parent.nodes.splice(ind, 0, tmp); + } + if (nd.length > 0) { + this.insert(tmp, null, nd); + } + } + this.refresh(parent.id); + return tmp; + }, + + remove: function () { // multiple arguments + var deleted = 0; + var tmp; + for (var a = 0; a < arguments.length; a++) { + tmp = this.get(arguments[a]); + if (tmp === null) continue; + if (this.selected !== null && this.selected === tmp.id) { + this.selected = null; + } + var ind = this.get(tmp.parent, arguments[a], true); + if (ind === null) continue; + if (tmp.parent.nodes[ind].selected) tmp.sidebar.unselect(tmp.id); + tmp.parent.nodes.splice(ind, 1); + deleted++; + } + if (deleted > 0 && arguments.length == 1) this.refresh(tmp.parent.id); else this.refresh(); + return deleted; + }, + + set: function (parent, id, node) { + if (arguments.length == 2) { + // need to be in reverse order + node = id; + id = parent; + parent = this; + } + // searches all nested nodes + if (typeof parent == 'string') parent = this.get(parent); + if (parent.nodes == null) return null; + for (var i = 0; i < parent.nodes.length; i++) { + if (parent.nodes[i].id === id) { + // make sure nodes inserted correctly + var nodes = node.nodes; + $.extend(parent.nodes[i], node, { nodes: [] }); + if (nodes != null) { + this.add(parent.nodes[i], nodes); + } + this.refresh(id); + return true; + } else { + var rv = this.set(parent.nodes[i], id, node); + if (rv) return true; + } + } + return false; + }, + + get: function (parent, id, returnIndex) { // can be just called get(id) or get(id, true) + if (arguments.length === 0) { + var all = []; + var tmp = this.find({}); + for (var t = 0; t < tmp.length; t++) { + if (tmp[t].id != null) all.push(tmp[t].id); + } + return all; + } else { + if (arguments.length == 1 || (arguments.length == 2 && id === true) ) { + // need to be in reverse order + returnIndex = id; + id = parent; + parent = this; + } + // searches all nested nodes + if (typeof parent == 'string') parent = this.get(parent); + if (parent.nodes == null) return null; + for (var i = 0; i < parent.nodes.length; i++) { + if (parent.nodes[i].id == id) { + if (returnIndex === true) return i; else return parent.nodes[i]; + } else { + var rv = this.get(parent.nodes[i], id, returnIndex); + if (rv || rv === 0) return rv; + } + } + return null; + } + }, + + find: function (parent, params, results) { // can be just called find({ selected: true }) + if (arguments.length == 1) { + // need to be in reverse order + params = parent; + parent = this; + } + if (!results) results = []; + // searches all nested nodes + if (typeof parent == 'string') parent = this.get(parent); + if (parent.nodes == null) return results; + for (var i = 0; i < parent.nodes.length; i++) { + var match = true; + for (var prop in params) { + if (parent.nodes[i][prop] != params[prop]) match = false; + } + if (match) results.push(parent.nodes[i]); + if (parent.nodes[i].nodes.length > 0) results = this.find(parent.nodes[i], params, results); + } + return results; + }, + + hide: function () { // multiple arguments + var hidden = 0; + for (var a = 0; a < arguments.length; a++) { + var tmp = this.get(arguments[a]); + if (tmp === null) continue; + tmp.hidden = true; + hidden++; + } + if (arguments.length == 1) this.refresh(arguments[0]); else this.refresh(); + return hidden; + }, + + show: function () { // multiple arguments + var shown = 0; + for (var a = 0; a < arguments.length; a++) { + var tmp = this.get(arguments[a]); + if (tmp === null) continue; + tmp.hidden = false; + shown++; + } + if (arguments.length == 1) this.refresh(arguments[0]); else this.refresh(); + return shown; + }, + + disable: function () { // multiple arguments + var disabled = 0; + for (var a = 0; a < arguments.length; a++) { + var tmp = this.get(arguments[a]); + if (tmp === null) continue; + tmp.disabled = true; + if (tmp.selected) this.unselect(tmp.id); + disabled++; + } + if (arguments.length == 1) this.refresh(arguments[0]); else this.refresh(); + return disabled; + }, + + enable: function () { // multiple arguments + var enabled = 0; + for (var a = 0; a < arguments.length; a++) { + var tmp = this.get(arguments[a]); + if (tmp === null) continue; + tmp.disabled = false; + enabled++; + } + if (arguments.length == 1) this.refresh(arguments[0]); else this.refresh(); + return enabled; + }, + + select: function (id) { + var new_node = this.get(id); + if (!new_node) return false; + if (this.selected == id && new_node.selected) return false; + this.unselect(this.selected); + $(this.box).find('#node_'+ w2utils.escapeId(id)) + .addClass('w2ui-selected') + .find('.w2ui-icon').addClass('w2ui-icon-selected'); + new_node.selected = true; + this.selected = id; + return true; + }, + + unselect: function (id) { + var current = this.get(id); + if (!current) return false; + current.selected = false; + $(this.box).find('#node_'+ w2utils.escapeId(id)) + .removeClass('w2ui-selected') + .find('.w2ui-icon').removeClass('w2ui-icon-selected'); + if (this.selected == id) this.selected = null; + return true; + }, + + toggle: function(id) { + var nd = this.get(id); + if (nd === null) return false; + if (nd.plus) { + this.set(id, { plus: false }); + this.expand(id); + this.refresh(id); + return; + } + if (nd.nodes.length === 0) return false; + if (this.get(id).expanded) return this.collapse(id); else return this.expand(id); + }, + + collapse: function (id) { + var obj = this; + var nd = this.get(id); + // event before + var eventData = this.trigger({ phase: 'before', type: 'collapse', target: id, object: nd }); + if (eventData.isCancelled === true) return; + // default action + $(this.box).find('#node_'+ w2utils.escapeId(id) +'_sub').slideUp(200); + $(this.box).find('#node_'+ w2utils.escapeId(id) +' .w2ui-node-dots:first-child').html('<div class="w2ui-expand">+</div>'); + nd.expanded = false; + // event after + this.trigger($.extend(eventData, { phase: 'after' })); + setTimeout(function () { obj.refresh(id); }, 200); + return true; + }, + + collapseAll: function (parent) { + if (typeof parent == 'undefined') parent = this; + if (typeof parent == 'string') parent = this.get(parent); + if (parent.nodes == null) return false; + for (var i = 0; i < parent.nodes.length; i++) { + if (parent.nodes[i].expanded === true) parent.nodes[i].expanded = false; + if (parent.nodes[i].nodes && parent.nodes[i].nodes.length > 0) this.collapseAll(parent.nodes[i]); + } + this.refresh(parent.id); + return true; + }, + + expand: function (id) { + var obj = this; + var nd = this.get(id); + // event before + var eventData = this.trigger({ phase: 'before', type: 'expand', target: id, object: nd }); + if (eventData.isCancelled === true) return; + // default action + $(this.box).find('#node_'+ w2utils.escapeId(id) +'_sub').slideDown(200); + $(this.box).find('#node_'+ w2utils.escapeId(id) +' .w2ui-node-dots:first-child').html('<div class="w2ui-expand">-</div>'); + nd.expanded = true; + // event after + this.trigger($.extend(eventData, { phase: 'after' })); + setTimeout(function () { obj.refresh(id); }, 200); + return true; + }, + + expandAll: function (parent) { + if (typeof parent == 'undefined') parent = this; + if (typeof parent == 'string') parent = this.get(parent); + if (parent.nodes == null) return false; + for (var i = 0; i < parent.nodes.length; i++) { + if (parent.nodes[i].expanded === false) parent.nodes[i].expanded = true; + if (parent.nodes[i].nodes && parent.nodes[i].nodes.length > 0) this.collapseAll(parent.nodes[i]); + } + this.refresh(parent.id); + }, + + expandParents: function (id) { + var node = this.get(id); + if (node === null) return false; + if (node.parent) { + node.parent.expanded = true; + this.expandParents(node.parent.id); + } + this.refresh(id); + return true; + }, + + click: function (id, event) { + var obj = this; + var nd = this.get(id); + if (nd === null) return; + if (nd.disabled || nd.group) return; // should click event if already selected + // unselect all previsously + $(obj.box).find('.w2ui-node.w2ui-selected').each(function (index, el) { + var oldID = $(el).attr('id').replace('node_', ''); + var oldNode = obj.get(oldID); + if (oldNode != null) oldNode.selected = false; + $(el).removeClass('w2ui-selected').find('.w2ui-icon').removeClass('w2ui-icon-selected'); + }); + // select new one + var newNode = $(obj.box).find('#node_'+ w2utils.escapeId(id)); + var oldNode = $(obj.box).find('#node_'+ w2utils.escapeId(obj.selected)); + newNode.addClass('w2ui-selected').find('.w2ui-icon').addClass('w2ui-icon-selected'); + // need timeout to allow rendering + setTimeout(function () { + // event before + var eventData = obj.trigger({ phase: 'before', type: 'click', target: id, originalEvent: event, node: nd, object: nd }); + if (eventData.isCancelled === true) { + // restore selection + newNode.removeClass('w2ui-selected').find('.w2ui-icon').removeClass('w2ui-icon-selected'); + oldNode.addClass('w2ui-selected').find('.w2ui-icon').addClass('w2ui-icon-selected'); + return; + } + // default action + if (oldNode !== null) oldNode.selected = false; + obj.get(id).selected = true; + obj.selected = id; + // route processing + if (nd.route) { + var route = String('/'+ nd.route).replace(/\/{2,}/g, '/'); + var info = w2utils.parseRoute(route); + if (info.keys.length > 0) { + for (var k = 0; k < info.keys.length; k++) { + if (obj.routeData[info.keys[k].name] == null) continue; + route = route.replace((new RegExp(':'+ info.keys[k].name, 'g')), obj.routeData[info.keys[k].name]); + } + } + setTimeout(function () { window.location.hash = route; }, 1); + } + // event after + obj.trigger($.extend(eventData, { phase: 'after' })); + }, 1); + }, + + keydown: function (event) { + var obj = this; + var nd = obj.get(obj.selected); + if (!nd || obj.keyboard !== true) return; + // trigger event + var eventData = obj.trigger({ phase: 'before', type: 'keydown', target: obj.name, originalEvent: event }); + if (eventData.isCancelled === true) return; + // default behaviour + if (event.keyCode == 13 || event.keyCode == 32) { // enter or space + if (nd.nodes.length > 0) obj.toggle(obj.selected); + } + if (event.keyCode == 37) { // left + if (nd.nodes.length > 0 && nd.expanded) { + obj.collapse(obj.selected); + } else { + selectNode(nd.parent); + if (!nd.parent.group) obj.collapse(nd.parent.id); + } + } + if (event.keyCode == 39) { // right + if ((nd.nodes.length > 0 || nd.plus) && !nd.expanded) obj.expand(obj.selected); + } + if (event.keyCode == 38) { // up + selectNode(neighbor(nd, prev)); + } + if (event.keyCode == 40) { // down + selectNode(neighbor(nd, next)); + } + // cancel event if needed + if ($.inArray(event.keyCode, [13, 32, 37, 38, 39, 40]) != -1) { + if (event.preventDefault) event.preventDefault(); + if (event.stopPropagation) event.stopPropagation(); + } + // event after + obj.trigger($.extend(eventData, { phase: 'after' })); + + function selectNode (node, event) { + if (node !== null && !node.hidden && !node.disabled && !node.group) { + obj.click(node.id, event); + setTimeout(function () { obj.scrollIntoView(); }, 50); + } + } + + function neighbor (node, neighborFunc) { + node = neighborFunc(node); + while (node !== null && (node.hidden || node.disabled)) { + if (node.group) break; else node = neighborFunc(node); + } + return node; + } + + function next (node, noSubs) { + if (node === null) return null; + var parent = node.parent; + var ind = obj.get(node.id, true); + var nextNode = null; + // jump inside + if (node.expanded && node.nodes.length > 0 && noSubs !== true) { + var t = node.nodes[0]; + if (t.hidden || t.disabled || t.group) nextNode = next(t); else nextNode = t; + } else { + if (parent && ind + 1 < parent.nodes.length) { + nextNode = parent.nodes[ind + 1]; + } else { + nextNode = next(parent, true); // jump to the parent + } + } + if (nextNode !== null && (nextNode.hidden || nextNode.disabled || nextNode.group)) nextNode = next(nextNode); + return nextNode; + } + + function prev (node) { + if (node === null) return null; + var parent = node.parent; + var ind = obj.get(node.id, true); + var prevNode = (ind > 0) ? lastChild(parent.nodes[ind - 1]) : parent; + if (prevNode !== null && (prevNode.hidden || prevNode.disabled || prevNode.group)) prevNode = prev(prevNode); + return prevNode; + } + + function lastChild (node) { + if (node.expanded && node.nodes.length > 0) { + var t = node.nodes[node.nodes.length - 1]; + if (t.hidden || t.disabled || t.group) return prev(t); else return lastChild(t); + } + return node; + } + }, + + scrollIntoView: function (id) { + if (typeof id == 'undefined') id = this.selected; + var nd = this.get(id); + if (nd === null) return; + var body = $(this.box).find('.w2ui-sidebar-div'); + var item = $(this.box).find('#node_'+ w2utils.escapeId(id)); + var offset = item.offset().top - body.offset().top; + if (offset + item.height() > body.height()) { + body.animate({ 'scrollTop': body.scrollTop() + body.height() / 1.3 }, 250, 'linear'); + } + if (offset <= 0) { + body.animate({ 'scrollTop': body.scrollTop() - body.height() / 1.3 }, 250, 'linear'); + } + }, + + dblClick: function (id, event) { + // if (window.getSelection) window.getSelection().removeAllRanges(); // clear selection + var nd = this.get(id); + // event before + var eventData = this.trigger({ phase: 'before', type: 'dblClick', target: id, originalEvent: event, object: nd }); + if (eventData.isCancelled === true) return; + // default action + this.toggle(id); + // event after + this.trigger($.extend(eventData, { phase: 'after' })); + }, + + contextMenu: function (id, event) { + var obj = this; + var nd = obj.get(id); + if (id != obj.selected) obj.click(id); + // need timeout to allow click to finish first + setTimeout(function () { + // event before + var eventData = obj.trigger({ phase: 'before', type: 'contextMenu', target: id, originalEvent: event, object: nd }); + if (eventData.isCancelled === true) return; + // default action + if (nd.group || nd.disabled) return; + if (obj.menu.length > 0) { + $(obj.box).find('#node_'+ w2utils.escapeId(id)) + .w2menu(obj.menu, { + left : (event ? event.offsetX || event.pageX : 50) - 25, + onSelect: function (event) { + obj.menuClick(id, parseInt(event.index), event.originalEvent); + } + } + ); + } + // event after + obj.trigger($.extend(eventData, { phase: 'after' })); + }, 150); // need timer 150 for FF + }, + + menuClick: function (itemId, index, event) { + var obj = this; + // event before + var eventData = obj.trigger({ phase: 'before', type: 'menuClick', target: itemId, originalEvent: event, menuIndex: index, menuItem: obj.menu[index] }); + if (eventData.isCancelled === true) return; + // default action + // -- empty + // event after + obj.trigger($.extend(eventData, { phase: 'after' })); + }, + + render: function (box) { + var time = (new Date()).getTime(); + // event before + var eventData = this.trigger({ phase: 'before', type: 'render', target: this.name, box: box }); + if (eventData.isCancelled === true) return; + // default action + if (typeof box != 'undefined' && box !== null) { + if ($(this.box).find('> div > div.w2ui-sidebar-div').length > 0) { + $(this.box) + .removeAttr('name') + .removeClass('w2ui-reset w2ui-sidebar') + .html(''); + } + this.box = box; + } + if (!this.box) return; + $(this.box) + .attr('name', this.name) + .addClass('w2ui-reset w2ui-sidebar') + .html('<div>'+ + '<div class="w2ui-sidebar-top"></div>' + + '<div class="w2ui-sidebar-div"></div>'+ + '<div class="w2ui-sidebar-bottom"></div>'+ + '</div>' + ); + $(this.box).find('> div').css({ + width : $(this.box).width() + 'px', + height: $(this.box).height() + 'px' + }); + if ($(this.box).length > 0) $(this.box)[0].style.cssText += this.style; + // adjust top and bottom + if (this.topHTML !== '') { + $(this.box).find('.w2ui-sidebar-top').html(this.topHTML); + $(this.box).find('.w2ui-sidebar-div') + .css('top', $(this.box).find('.w2ui-sidebar-top').height() + 'px'); + } + if (this.bottomHTML !== '') { + $(this.box).find('.w2ui-sidebar-bottom').html(this.bottomHTML); + $(this.box).find('.w2ui-sidebar-div') + .css('bottom', $(this.box).find('.w2ui-sidebar-bottom').height() + 'px'); + } + // event after + this.trigger($.extend(eventData, { phase: 'after' })); + // --- + this.refresh(); + return (new Date()).getTime() - time; + }, + + refresh: function (id) { + var time = (new Date()).getTime(); + // if (window.getSelection) window.getSelection().removeAllRanges(); // clear selection + // event before + var eventData = this.trigger({ phase: 'before', type: 'refresh', target: (typeof id != 'undefined' ? id : this.name) }); + if (eventData.isCancelled === true) return; + // adjust top and bottom + if (this.topHTML !== '') { + $(this.box).find('.w2ui-sidebar-top').html(this.topHTML); + $(this.box).find('.w2ui-sidebar-div') + .css('top', $(this.box).find('.w2ui-sidebar-top').height() + 'px'); + } + if (this.bottomHTML !== '') { + $(this.box).find('.w2ui-sidebar-bottom').html(this.bottomHTML); + $(this.box).find('.w2ui-sidebar-div') + .css('bottom', $(this.box).find('.w2ui-sidebar-bottom').height() + 'px'); + } + // default action + $(this.box).find('> div').css({ + width : $(this.box).width() + 'px', + height: $(this.box).height() + 'px' + }); + var obj = this; + var node, nd; + var nm; + if (typeof id == 'undefined') { + node = this; + nm = '.w2ui-sidebar-div'; + } else { + node = this.get(id); + if (node === null) return; + nm = '#node_'+ w2utils.escapeId(node.id) + '_sub'; + } + var nodeHTML; + if (node !== this) { + var tmp = '#node_'+ w2utils.escapeId(node.id); + nodeHTML = getNodeHTML(node); + $(this.box).find(tmp).before('<div id="sidebar_'+ this.name + '_tmp"></div>'); + $(this.box).find(tmp).remove(); + $(this.box).find(nm).remove(); + $('#sidebar_'+ this.name + '_tmp').before(nodeHTML); + $('#sidebar_'+ this.name + '_tmp').remove(); + } + // refresh sub nodes + $(this.box).find(nm).html(''); + for (var i = 0; i < node.nodes.length; i++) { + nd = node.nodes[i]; + nodeHTML = getNodeHTML(nd); + $(this.box).find(nm).append(nodeHTML); + if (nd.nodes.length !== 0) { this.refresh(nd.id); } + } + // event after + this.trigger($.extend(eventData, { phase: 'after' })); + return (new Date()).getTime() - time; + + function getNodeHTML(nd) { + var html = ''; + var img = nd.img; + if (img === null) img = this.img; + var icon = nd.icon; + if (icon === null) icon = this.icon; + // -- find out level + var tmp = nd.parent; + var level = 0; + while (tmp && tmp.parent !== null) { + if (tmp.group) level--; + tmp = tmp.parent; + level++; + } + if (typeof nd.caption != 'undefined') nd.text = nd.caption; + if (nd.group) { + html = + '<div class="w2ui-node-group" id="node_'+ nd.id +'"'+ + ' onclick="w2ui[\''+ obj.name +'\'].toggle(\''+ nd.id +'\')"'+ + ' onmouseout="$(this).find(\'span:nth-child(1)\').css(\'color\', \'transparent\')" '+ + ' onmouseover="$(this).find(\'span:nth-child(1)\').css(\'color\', \'inherit\')">'+ + (nd.groupShowHide ? '<span>'+ (!nd.hidden && nd.expanded ? w2utils.lang('Hide') : w2utils.lang('Show')) +'</span>' : '<span></span>') + + ' <span>'+ nd.text +'</span>'+ + '</div>'+ + '<div class="w2ui-node-sub" id="node_'+ nd.id +'_sub" style="'+ nd.style +';'+ (!nd.hidden && nd.expanded ? '' : 'display: none;') +'"></div>'; + } else { + if (nd.selected && !nd.disabled) obj.selected = nd.id; + tmp = ''; + if (img) tmp = '<div class="w2ui-node-image w2ui-icon '+ img + (nd.selected && !nd.disabled ? " w2ui-icon-selected" : "") +'"></div>'; + if (icon) tmp = '<div class="w2ui-node-image"><span class="'+ icon +'"></span></div>'; + html = + '<div class="w2ui-node '+ (nd.selected ? 'w2ui-selected' : '') +' '+ (nd.disabled ? 'w2ui-disabled' : '') +'" id="node_'+ nd.id +'" style="'+ (nd.hidden ? 'display: none;' : '') +'"'+ + ' ondblclick="w2ui[\''+ obj.name +'\'].dblClick(\''+ nd.id +'\', event);"'+ + ' oncontextmenu="w2ui[\''+ obj.name +'\'].contextMenu(\''+ nd.id +'\', event); '+ + ' if (event.preventDefault) event.preventDefault();"'+ + ' onClick="w2ui[\''+ obj.name +'\'].click(\''+ nd.id +'\', event); ">'+ + '<table cellpadding="0" cellspacing="0" style="margin-left:'+ (level*18) +'px; padding-right:'+ (level*18) +'px"><tr>'+ + '<td class="w2ui-node-dots" nowrap onclick="w2ui[\''+ obj.name +'\'].toggle(\''+ nd.id +'\'); '+ + ' if (event.stopPropagation) event.stopPropagation(); else event.cancelBubble = true;">'+ + ' <div class="w2ui-expand">' + (nd.nodes.length > 0 ? (nd.expanded ? '-' : '+') : (nd.plus ? '+' : '')) + '</div>' + + '</td>'+ + '<td class="w2ui-node-data" nowrap>'+ + tmp + + (nd.count || nd.count === 0 ? '<div class="w2ui-node-count">'+ nd.count +'</div>' : '') + + '<div class="w2ui-node-caption">'+ nd.text +'</div>'+ + '</td>'+ + '</tr></table>'+ + '</div>'+ + '<div class="w2ui-node-sub" id="node_'+ nd.id +'_sub" style="'+ nd.style +';'+ (!nd.hidden && nd.expanded ? '' : 'display: none;') +'"></div>'; + } + return html; + } + }, + + resize: function () { + var time = (new Date()).getTime(); + // if (window.getSelection) window.getSelection().removeAllRanges(); // clear selection + // event before + var eventData = this.trigger({ phase: 'before', type: 'resize', target: this.name }); + if (eventData.isCancelled === true) return; + // default action + $(this.box).css('overflow', 'hidden'); // container should have no overflow + //$(this.box).find('.w2ui-sidebar-div').css('overflow', 'hidden'); + $(this.box).find('> div').css({ + width : $(this.box).width() + 'px', + height : $(this.box).height() + 'px' + }); + //$(this.box).find('.w2ui-sidebar-div').css('overflow', 'auto'); + // event after + this.trigger($.extend(eventData, { phase: 'after' })); + return (new Date()).getTime() - time; + }, + + destroy: function () { + // event before + var eventData = this.trigger({ phase: 'before', type: 'destroy', target: this.name }); + if (eventData.isCancelled === true) return; + // clean up + if ($(this.box).find('> div > div.w2ui-sidebar-div').length > 0) { + $(this.box) + .removeAttr('name') + .removeClass('w2ui-reset w2ui-sidebar') + .html(''); + } + delete w2ui[this.name]; + // event after + this.trigger($.extend(eventData, { phase: 'after' })); + }, + + lock: function (msg, showSpinner) { + var box = $(this.box).find('> div:first-child'); + var args = Array.prototype.slice.call(arguments, 0); + args.unshift(box); + w2utils.lock.apply(window, args); + }, + + unlock: function () { + w2utils.unlock(this.box); + } + }; + + $.extend(w2sidebar.prototype, w2utils.event); + w2obj.sidebar = w2sidebar; +})(); + +/************************************************************************ +* Library: Web 2.0 UI for jQuery (using prototypical inheritance) +* - Following objects defined +* - w2field - various field controls +* - $().w2field - jQuery wrapper +* - Dependencies: jQuery, w2utils +* +* == NICE TO HAVE == +* - upload (regular files) +* - BUG with prefix/postfix and arrows (test in different contexts) +* - prefix and suffix are slow (100ms or so) +* - multiple date selection +* - month selection, year selections +* - arrows no longer work (for int) +* - form to support custom types +* - bug: if input is hidden and then enum is applied, then when it becomes visible, it will be 110px +* +************************************************************************/ + +(function ($) { + + var w2field = function (options) { + // public properties + this.el = null + this.helpers = {}; // object or helper elements + this.type = options.type || 'text'; + this.options = $.extend(true, {}, options); + this.onSearch = options.onSearch || null; + this.onRequest = options.onRequest || null; + this.onLoad = options.onLoad || null; + this.onError = options.onError || null; + this.onClick = options.onClick || null; + this.onAdd = options.onAdd || null; + this.onNew = options.onNew || null; + this.onRemove = options.onRemove || null; + this.onMouseOver = options.onMouseOver || null; + this.onMouseOut = options.onMouseOut || null; + this.onIconClick = options.onIconClick || null; + this.tmp = {}; // temp object + // clean up some options + delete this.options.type; + delete this.options.onSearch; + delete this.options.onRequest; + delete this.options.onLoad; + delete this.options.onError; + delete this.options.onClick; + delete this.options.onMouseOver; + delete this.options.onMouseOut; + delete this.options.onIconClick; + // extend with defaults + $.extend(true, this, w2obj.field); + }; + + // ==================================================== + // -- Registers as a jQuery plugin + + $.fn.w2field = function (method, options) { + // call direct + if (this.length == 0) { + var pr = w2field.prototype; + if (pr[method]) { + return pr[method].apply(pr, Array.prototype.slice.call(arguments, 1)); + } + } else { + if (typeof method == 'string' && typeof options == 'object') { + method = $.extend(true, {}, options, { type: method }); + } + if (typeof method == 'string' && typeof options == 'undefined') { + method = { type: method }; + } + method.type = String(method.type).toLowerCase(); + return this.each(function (index, el) { + var obj = $(el).data('w2field'); + // if object is not defined, define it + if (typeof obj == 'undefined') { + var obj = new w2field(method); + $.extend(obj, { handlers: [] }); + if (el) obj.el = $(el)[0]; + obj.init(); + $(el).data('w2field', obj); + return obj; + } else { // fully re-init + obj.clear(); + if (method.type == 'clear') return; + var obj = new w2field(method); + $.extend(obj, { handlers: [] }); + if (el) obj.el = $(el)[0]; + obj.init(); + $(el).data('w2field', obj); + return obj; + } + return null; + }); + } + } + + // ==================================================== + // -- Implementation of core functionality + + /* To add custom types + $().w2field('addType', 'myType', function (options) { + $(this.el).on('keypress', function (event) { + if (event.metaKey || event.ctrlKey || event.altKey + || (event.charCode != event.keyCode && event.keyCode > 0)) return; + var ch = String.fromCharCode(event.charCode); + if (ch != 'a' && ch != 'b' && ch != 'c') { + if (event.stopPropagation) event.stopPropagation(); else event.cancelBubble = true; + return false; + } + }); + $(this.el).on('blur', function (event) { // keyCode & charCode differ in FireFox + var ch = this.value; + if (ch != 'a' && ch != 'b' && ch != 'c') { + $(this).w2tag(w2utils.lang("Not a single charecter from the set of 'abc'")); + } + }); + }); + */ + + w2field.prototype = { + + custom: {}, // map of custom types + + pallete: [ + ['000000', '444444', '666666', '999999', 'CCCCCC', 'EEEEEE', 'F3F3F3', 'FFFFFF'], + ['FF011B', 'FF9838', 'FFFD59', '01FD55', '00FFFE', '0424F3', '9B24F4', 'FF21F5'], + ['F4CCCC', 'FCE5CD', 'FFF2CC', 'D9EAD3', 'D0E0E3', 'CFE2F3', 'D9D1E9', 'EAD1DC'], + ['EA9899', 'F9CB9C', 'FEE599', 'B6D7A8', 'A2C4C9', '9FC5E8', 'B4A7D6', 'D5A6BD'], + ['E06666', 'F6B26B', 'FED966', '93C47D', '76A5AF', '6FA8DC', '8E7CC3', 'C27BA0'], + ['CC0814', 'E69138', 'F1C232', '6AA84F', '45818E', '3D85C6', '674EA7', 'A54D79'], + ['99050C', 'B45F17', 'BF901F', '37761D', '124F5C', '0A5394', '351C75', '741B47'], + ['660205', '783F0B', '7F6011', '274E12', '0C343D', '063762', '20124D', '4C1030'] + ], + + addType: function (type, handler) { + type = String(type).toLowerCase(); + this.custom[type] = handler; + return true; + }, + + removeType: function (type) { + type = String(type).toLowerCase(); + if (!this.custom[type]) return false; + delete this.custom[type]; + return true + }, + + init: function () { + var obj = this; + var options = this.options; + var defaults; + + // Custom Types + if (typeof this.custom[this.type] == 'function') { + this.custom[this.type].call(this, options); + return; + } + // only for INPUT or TEXTAREA + if (['INPUT', 'TEXTAREA'].indexOf(this.el.tagName) == -1) { + console.log('ERROR: w2field could only be applied to INPUT or TEXTAREA.', this.el); + return; + } + + switch (this.type) { + case 'text': + case 'int': + case 'float': + case 'money': + case 'currency': + case 'percent': + case 'alphanumeric': + case 'hex': + defaults = { + min : null, + max : null, + step : 1, + placeholder : '', + autoFormat : true, + currencyPrefix : w2utils.settings.currencyPrefix, + currencySuffix : w2utils.settings.currencySuffix, + currencyPrecision : w2utils.settings.currencyPrecision, + groupSymbol : w2utils.settings.groupSymbol, + arrows : false, + keyboard : true, + precision : null, + silent : true, + prefix : '', + suffix : '' + }; + this.options = $.extend(true, {}, defaults, options); + options = this.options; // since object is re-created, need to re-assign + options.numberRE = new RegExp('['+ options.groupSymbol + ']', 'g'); + options.moneyRE = new RegExp('['+ options.currencyPrefix + options.currencySuffix + options.groupSymbol + ']', 'g'); + options.percentRE = new RegExp('['+ options.groupSymbol + '%]', 'g'); + // no keyboard support needed + if (['text', 'alphanumeric', 'hex'].indexOf(this.type) != -1) { + options.arrows = false; + options.keyboard = false; + } + this.addPrefix(); // only will add if needed + this.addSuffix(); + $(this.el).attr('placeholder', options.placeholder); + break; + + case 'color': + defaults = { + prefix : '#', + suffix : '<div style="width: '+ (parseInt($(this.el).css('font-size')) || 12) +'px"> </div>', + placeholder : '', + arrows : false, + keyboard : false + }; + $.extend(options, defaults); + this.addPrefix(); // only will add if needed + this.addSuffix(); // only will add if needed + // additional checks + $(this.el).attr('maxlength', 6); + if ($(this.el).val() != '') setTimeout(function () { $(obj.el).change(); }, 1); + $(this.el).attr('placeholder', options.placeholder); + break; + + case 'date': + defaults = { + format : w2utils.settings.date_format, // date format + placeholder : '', + keyboard : true, + silent : true, + start : '', // string or jquery object + end : '', // string or jquery object + blocked : {}, // { '4/11/2011': 'yes' } + colored : {} // { '4/11/2011': 'red:white' } + }; + this.options = $.extend(true, {}, defaults, options); + options = this.options; // since object is re-created, need to re-assign + $(this.el).attr('placeholder', options.placeholder ? options.placeholder : options.format); + break; + + case 'time': + defaults = { + format : w2utils.settings.time_format, + placeholder : '', + keyboard : true, + silent : true, + start : '', + end : '' + }; + this.options = $.extend(true, {}, defaults, options); + options = this.options; // since object is re-created, need to re-assign + $(this.el).attr('placeholder', options.placeholder ? options.placeholder : (options.format == 'h12' ? 'hh:mi pm' : 'hh:mi')); + break; + + case 'datetime': + break; + + case 'list': + case 'combo': + defaults = { + items : [], + selected : {}, + placeholder : '', + url : null, // url to pull data from + postData : {}, + minLength : 1, + cacheMax : 250, + maxDropHeight : 350, // max height for drop down menu + match : 'begins', // ['contains', 'is', 'begins', 'ends'] + silent : true, + icon : null, + iconStyle : '', + onSearch : null, // when search needs to be performed + onRequest : null, // when request is submitted + onLoad : null, // when data is received + onError : null, // when data fails to load due to server error or other failure modes + onIconClick : null, + renderDrop : null, // render function for drop down item + prefix : '', + suffix : '', + openOnFocus : false, // if to show overlay onclick or when typing + markSearch : false + }; + options.items = this.normMenu(options.items); // need to be first + if (this.type == 'list') { + // defaults.search = (options.items && options.items.length >= 10 ? true : false); + defaults.openOnFocus = true; + defaults.suffix = '<div class="arrow-down" style="margin-top: '+ ((parseInt($(this.el).height()) - 6) / 2) +'px;"></div>'; + $(this.el).addClass('w2ui-select'); + // if simple value - look it up + if (!$.isPlainObject(options.selected)) { + for (var i in options.items) { + var item = options.items[i]; + if (item && item.id == options.selected) { + options.selected = $.extend(true, {}, item); + break; + } + } + } + } + options = $.extend({}, defaults, options, { + align : 'both', // same width as control + altRows : true // alternate row color + }); + this.options = options; + if (!$.isPlainObject(options.selected)) options.selected = {}; + $(this.el).data('selected', options.selected); + if (options.url) this.request(0); + if (this.type == 'list') this.addFocus(); + this.addPrefix(); + this.addSuffix(); + setTimeout(function () { obj.refresh(); }, 10); // need this for icon refresh + $(this.el).attr('placeholder', options.placeholder).attr('autocomplete', 'off'); + if (typeof options.selected.text != 'undefined') $(this.el).val(options.selected.text); + break; + + case 'enum': + defaults = { + items : [], + selected : [], + placeholder : '', + max : 0, // max number of selected items, 0 - unlim + url : null, // not implemented + postData : {}, + minLength : 1, + cacheMax : 250, + maxWidth : 250, // max width for a single item + maxHeight : 350, // max height for input control to grow + maxDropHeight : 350, // max height for drop down menu + match : 'contains', // ['contains', 'is', 'begins', 'ends'] + silent : true, + openOnFocus : false, // if to show overlay onclick or when typing + markSearch : true, + renderDrop : null, // render function for drop down item + renderItem : null, // render selected item + style : '', // style for container div + onSearch : null, // when search needs to be performed + onRequest : null, // when request is submitted + onLoad : null, // when data is received + onError : null, // when data fails to load due to server error or other failure modes + onClick : null, // when an item is clicked + onAdd : null, // when an item is added + onNew : null, // when new item should be added + onRemove : null, // when an item is removed + onMouseOver : null, // when an item is mouse over + onMouseOut : null // when an item is mouse out + }; + options = $.extend({}, defaults, options, { + align : 'both', // same width as control + suffix : '', + altRows : true // alternate row color + }); + options.items = this.normMenu(options.items); + options.selected = this.normMenu(options.selected); + this.options = options; + if (!$.isArray(options.selected)) options.selected = []; + $(this.el).data('selected', options.selected); + if (options.url) this.request(0); + this.addSuffix(); + this.addMulti(); + break; + + case 'file': + defaults = { + selected : [], + placeholder : w2utils.lang('Attach files by dragging and dropping or Click to Select'), + max : 0, + maxSize : 0, // max size of all files, 0 - unlim + maxFileSize : 0, // max size of a single file, 0 -unlim + maxWidth : 250, // max width for a single item + maxHeight : 350, // max height for input control to grow + maxDropHeight : 350, // max height for drop down menu + silent : true, + renderItem : null, // render selected item + style : '', // style for container div + onClick : null, // when an item is clicked + onAdd : null, // when an item is added + onRemove : null, // when an item is removed + onMouseOver : null, // when an item is mouse over + onMouseOut : null // when an item is mouse out + }; + options = $.extend({}, defaults, options, { + align : 'both', // same width as control + altRows : true // alternate row color + }); + this.options = options; + if (!$.isArray(options.selected)) options.selected = []; + $(this.el).data('selected', options.selected); + this.addMulti(); + break; + } + // attach events + this.tmp = { + onChange : function (event) { obj.change.call(obj, event) }, + onClick : function (event) { obj.click.call(obj, event) }, + onFocus : function (event) { obj.focus.call(obj, event) }, + onBlur : function (event) { obj.blur.call(obj, event) }, + onKeydown : function (event) { obj.keyDown.call(obj, event) }, + onKeyup : function (event) { obj.keyUp.call(obj, event) }, + onKeypress : function (event) { obj.keyPress.call(obj, event) } + } + $(this.el) + .addClass('w2field') + .data('w2field', this) + .on('change', this.tmp.onChange) + .on('click', this.tmp.onClick) // ignore click because it messes overlays + .on('focus', this.tmp.onFocus) + .on('blur', this.tmp.onBlur) + .on('keydown', this.tmp.onKeydown) + .on('keyup', this.tmp.onKeyup) + .on('keypress', this.tmp.onKeypress) + .css({ + 'box-sizing' : 'border-box', + '-webkit-box-sizing' : 'border-box', + '-moz-box-sizing' : 'border-box', + '-ms-box-sizing' : 'border-box', + '-o-box-sizing' : 'border-box' + }); + // format initial value + this.change($.Event('change')); + }, + + clear: function () { + var obj = this; + var options = this.options; + // if money then clear value + if (['money', 'currency'].indexOf(this.type) != -1) { + $(this.el).val($(this.el).val().replace(options.moneyRE, '')); + } + if (this.type == 'percent') { + $(this.el).val($(this.el).val().replace(/%/g, '')); + } + if (this.type == 'color') { + $(this.el).removeAttr('maxlength'); + } + if (this.type == 'list') { + $(this.el).removeClass('w2ui-select'); + } + if (['date', 'time'].indexOf(this.type) != -1) { + if ($(this.el).attr('placeholder') == options.format) $(this.el).attr('placeholder', ''); + } + this.type = 'clear'; + var tmp = $(this.el).data('tmp'); + if (!this.tmp) return; + // restore paddings + if (typeof tmp != 'undefined') { + if (tmp && tmp['old-padding-left']) $(this.el).css('padding-left', tmp['old-padding-left']); + if (tmp && tmp['old-padding-right']) $(this.el).css('padding-right', tmp['old-padding-right']); + } + // remove events and data + $(this.el) + .val(this.clean($(this.el).val())) + .removeClass('w2field') + .removeData() // removes all attached data + .off('change', this.tmp.onChange) + .off('click', this.tmp.onClick) + .off('focus', this.tmp.onFocus) + .off('blur', this.tmp.onBlur) + .off('keydown', this.tmp.onKeydown) + .off('keyup', this.tmp.onKeyup) + .off('keypress', this.tmp.onKeypress); + // remove helpers + for (var h in this.helpers) $(this.helpers[h]).remove(); + this.helpers = {}; + }, + + refresh: function () { + var obj = this; + var options = this.options; + var selected = $(this.el).data('selected'); + var time = (new Date()).getTime(); + // enum + if (['list'].indexOf(this.type) != -1) { + $(obj.el).parent().css('white-space', 'nowrap'); // needs this for arrow alway to appear on the right side + // hide focus and show text + if (obj.helpers.prefix) obj.helpers.prefix.hide(); + setTimeout(function () { + if (!obj.helpers.focus) return; + // if empty show no icon + if (!$.isEmptyObject(selected) && options.icon) { + options.prefix = '<span class="w2ui-icon '+ options.icon +'"style="cursor: pointer; font-size: 14px;' + + ' display: inline-block; margin-top: -1px; color: #7F98AD;'+ options.iconStyle +'">'+ + '</span>'; + obj.addPrefix(); + } else { + options.prefix = ''; + obj.addPrefix(); + } + // focus helpder + var focus = obj.helpers.focus.find('input'); + if ($(focus).val() == '') { + $(focus).css('opacity', 0).prev().css('opacity', 0); + $(obj.el).val(selected && selected.text != null ? selected.text : ''); + $(obj.el).attr('placeholder', $(obj.el).attr('_placeholder')); + } else { + $(focus).css('opacity', 1).prev().css('opacity', 1); + $(obj.el).val(''); + $(obj.el).attr('_placeholder', $(obj.el).attr('placeholder')).removeAttr('placeholder'); + setTimeout(function () { + if (obj.helpers.prefix) obj.helpers.prefix.hide(); + var tmp = 'position: absolute; opacity: 0; margin: 4px 0px 0px 2px; background-position: left !important;'; + if (options.icon) { + $(focus).css('margin-left', '17px'); + $(obj.helpers.focus).find('.icon-search').attr('style', tmp + 'width: 11px !important; opacity: 1'); + } else { + $(focus).css('margin-left', '0px'); + $(obj.helpers.focus).find('.icon-search').attr('style', tmp + 'width: 0px !important; opacity: 0'); + } + }, 1); + } + }, 1); + } + if (['enum', 'file'].indexOf(this.type) != -1) { + var html = ''; + for (var s in selected) { + var it = selected[s]; + var ren = ''; + if (typeof options.renderItem == 'function') { + ren = options.renderItem(it, s, '<div class="w2ui-list-remove" title="'+ w2utils.lang('Remove') +'" index="'+ s +'"> </div>'); + } else { + ren = '<div class="w2ui-list-remove" title="'+ w2utils.lang('Remove') +'" index="'+ s +'"> </div>'+ + (obj.type == 'enum' ? it.text : it.name + '<span class="file-size"> - '+ w2utils.size(it.size) +'</span>'); + } + html += '<li index="'+ s +'" style="max-width: '+ parseInt(options.maxWidth) + 'px; '+ (it.style ? it.style : '') +'">'+ + ren +'</li>'; + } + var div = obj.helpers.multi; + var ul = div.find('ul'); + div.attr('style', div.attr('style') + ';' + options.style); + if ($(obj.el).attr('readonly')) div.addClass('w2ui-readonly'); else div.removeClass('w2ui-readonly'); + // celan + div.find('.w2ui-enum-placeholder').remove(); + ul.find('li').not('li.nomouse').remove(); + // add new list + if (html != '') { + ul.prepend(html); + } else if (typeof options.placeholder != 'undefined') { + var style = + 'padding-top: ' + $(this.el).css('padding-top') + ';'+ + 'padding-left: ' + $(this.el).css('padding-left') + '; ' + + 'box-sizing: ' + $(this.el).css('box-sizing') + '; ' + + 'line-height: ' + $(this.el).css('line-height') + '; ' + + 'font-size: ' + $(this.el).css('font-size') + '; ' + + 'font-family: ' + $(this.el).css('font-family') + '; '; + div.prepend('<div class="w2ui-enum-placeholder" style="'+ style +'">'+ options.placeholder + '</div>'); + } + // ITEMS events + div.find('li') + .data('mouse', 'out') + .on('click', function (event) { + var item = selected[$(event.target).attr('index')]; + if ($(event.target).hasClass('nomouse')) return; + event.stopPropagation(); + // trigger event + var eventData = obj.trigger({ phase: 'before', type: 'click', target: obj.el, originalEvent: event.originalEvent, item: item }); + if (eventData.isCancelled === true) return; + // default behavior + if ($(event.target).hasClass('w2ui-list-remove')) { + if ($(obj.el).attr('readonly')) return; + // trigger event + var eventData = obj.trigger({ phase: 'before', type: 'remove', target: obj.el, originalEvent: event.originalEvent, item: item }); + if (eventData.isCancelled === true) return; + // default behavior + $().w2overlay(); + selected.splice($(event.target).attr('index'), 1); + $(obj.el).trigger('change'); + $(event.target).parent().fadeOut('fast'); + setTimeout(function () { + obj.refresh(); + // event after + obj.trigger($.extend(eventData, { phase: 'after' })); + }, 300); + } + if (obj.type == 'file' && !$(event.target).hasClass('w2ui-list-remove')) { + var preview = ''; + if ((/image/i).test(item.type)) { // image + preview = '<div style="padding: 3px;">'+ + ' <img src="'+ (item.content ? 'data:'+ item.type +';base64,'+ item.content : '') +'" style="max-width: 300px;" '+ + ' onload="var w = $(this).width(); var h = $(this).height(); '+ + ' if (w < 300 & h < 300) return; '+ + ' if (w >= h && w > 300) $(this).width(300);'+ + ' if (w < h && h > 300) $(this).height(300);"'+ + ' onerror="this.style.display = \'none\'"'+ + ' >'+ + '</div>'; + } + var td1 = 'style="padding: 3px; text-align: right; color: #777;"'; + var td2 = 'style="padding: 3px"'; + preview += '<div style="padding: 8px;">'+ + ' <table cellpadding="2">'+ + ' <tr><td '+ td1 +'>Name:</td><td '+ td2 +'>'+ item.name +'</td></tr>'+ + ' <tr><td '+ td1 +'>Size:</td><td '+ td2 +'>'+ w2utils.size(item.size) +'</td></tr>'+ + ' <tr><td '+ td1 +'>Type:</td><td '+ td2 +'>' + + ' <span style="width: 200px; display: block-inline; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;">'+ item.type +'</span>'+ + ' </td></tr>'+ + ' <tr><td '+ td1 +'>Modified:</td><td '+ td2 +'>'+ w2utils.date(item.modified) +'</td></tr>'+ + ' </table>'+ + '</div>'; + $(event.target).w2overlay(preview); + } + // event after + obj.trigger($.extend(eventData, { phase: 'after' })); + }) + .on('mouseover', function (event) { + var tmp = event.target; + if (tmp.tagName != 'LI') tmp = tmp.parentNode; + if ($(tmp).hasClass('nomouse')) return; + if ($(tmp).data('mouse') == 'out') { + var item = selected[$(tmp).attr('index')]; + // trigger event + var eventData = obj.trigger({ phase: 'before', type: 'mouseOver', target: obj.el, originalEvent: event.originalEvent, item: item }); + if (eventData.isCancelled === true) return; + // event after + obj.trigger($.extend(eventData, { phase: 'after' })); + } + $(tmp).data('mouse', 'over'); + }) + .on('mouseout', function (event) { + var tmp = event.target; + if (tmp.tagName != 'LI') tmp = tmp.parentNode; + if ($(tmp).hasClass('nomouse')) return; + $(tmp).data('mouse', 'leaving'); + setTimeout(function () { + if ($(tmp).data('mouse') == 'leaving') { + $(tmp).data('mouse', 'out'); + var item = selected[$(tmp).attr('index')]; + // trigger event + var eventData = obj.trigger({ phase: 'before', type: 'f', target: obj.el, originalEvent: event.originalEvent, item: item }); + if (eventData.isCancelled === true) return; + // event after + obj.trigger($.extend(eventData, { phase: 'after' })); + } + }, 0); + }); + // adjust height + $(this.el).height('auto'); + var cntHeight = $(div).find('> div').height() + w2utils.getSize(div, '+height') * 2; + if (cntHeight < 26) cntHeight = 26; + if (cntHeight > options.maxHeight) cntHeight = options.maxHeight; + if (div.length > 0) div[0].scrollTop = 1000; + var inpHeight = w2utils.getSize($(this.el), 'height') - 2; + if (inpHeight > cntHeight) cntHeight = inpHeight + $(div).css({ 'height': cntHeight + 'px', overflow: (cntHeight == options.maxHeight ? 'auto' : 'hidden') }); + if (cntHeight < options.maxHeight) $(div).prop('scrollTop', 0); + $(this.el).css({ 'height' : (cntHeight + 2) + 'px' }); + } + return (new Date()).getTime() - time; + }, + + reset: function () { + var obj = this; + var type = this.type; + this.clear(); + this.type = type; + this.init(); + }, + + clean: function (val) { + var options = this.options; + val = String(val).trim(); + // clean + if (['int', 'float', 'money', 'currency', 'percent'].indexOf(this.type) != -1) { + if (options.autoFormat && ['money', 'currency'].indexOf(this.type) != -1) val = String(val).replace(options.moneyRE, ''); + if (options.autoFormat && this.type == 'percent') val = String(val).replace(options.percentRE, ''); + if (options.autoFormat && ['int', 'float'].indexOf(this.type) != -1) val = String(val).replace(options.numberRE, ''); + if (parseFloat(val) == val) { + if (options.min !== null && val < options.min) { val = options.min; $(this.el).val(options.min); } + if (options.max !== null && val > options.max) { val = options.max; $(this.el).val(options.max); } + } + if (val !== '' && w2utils.isFloat(val)) val = Number(val); else val = ''; + } + return val; + }, + + format: function (val) { + var options = this.options; + // autoformat numbers or money + if (options.autoFormat && val != '') { + switch (this.type) { + case 'money': + case 'currency': + val = w2utils.formatNumber(Number(val).toFixed(options.currencyPrecision), options.groupSymbol); + if (val != '') val = options.currencyPrefix + val + options.currencySuffix; + break; + case 'percent': + val = w2utils.formatNumber(options.precision ? Number(val).toFixed(options.precision) : val, options.groupSymbol); + if (val != '') val += '%'; + break; + case 'float': + val = w2utils.formatNumber(options.precision ? Number(val).toFixed(options.precision) : val, options.groupSymbol); + break; + case 'int': + val = w2utils.formatNumber(val, options.groupSymbol); + break; + } + } + return val; + }, + + change: function (event) { + var obj = this; + var options = obj.options; + // numeric + if (['int', 'float', 'money', 'currency', 'percent'].indexOf(this.type) != -1) { + // check max/min + var val = $(this.el).val(); + var new_val = this.format(this.clean($(this.el).val())); + // if was modified + if (val != '' && val != new_val) { + $(this.el).val(new_val).change(); + // cancel event + event.stopPropagation(); + event.preventDefault(); + return false; + } + } + // color + if (this.type == 'color') { + var color = '#' + $(this.el).val(); + if ($(this.el).val().length != 6 && $(this.el).val().length != 3) color = ''; + $(this.el).next().find('div').css('background-color', color); + if ($(obj.el).is(':focus')) this.updateOverlay(); + } + }, + + click: function (event) { + event.stopPropagation(); + // lists + if (['list', 'combo', 'enum'].indexOf(this.type) != -1) { + if (!$(this.el).is(':focus')) this.focus(event); + } + // other fields with drops + if (['date', 'time', 'color'].indexOf(this.type) != -1) { + this.updateOverlay(); + } + }, + + focus: function (event) { + var obj = this; + var options = this.options; + // color, date, time + if (['color', 'date', 'time'].indexOf(obj.type) !== -1) { + if ($(obj.el).attr('readonly')) return; + if ($("#w2ui-overlay").length > 0) $('#w2ui-overlay')[0].hide(); + setTimeout(function () { obj.updateOverlay(); }, 150); + } + // menu + if (['list', 'combo', 'enum'].indexOf(obj.type) != -1) { + if ($(obj.el).attr('readonly')) return; + if ($("#w2ui-overlay").length > 0) $('#w2ui-overlay')[0].hide(); + setTimeout(function () { + if (obj.type == 'list' && $(obj.el).is(':focus')) { + $(obj.helpers.focus).find('input').focus(); + return; + } + obj.search(); + setTimeout(function () { obj.updateOverlay(); }, 1); + }, 1); + } + // file + if (obj.type == 'file') { + $(obj.helpers.multi).css({ 'outline': 'auto 5px #7DB4F3', 'outline-offset': '-2px' }); + } + }, + + blur: function (event) { + var obj = this; + var options = obj.options; + var val = $(obj.el).val().trim(); + // hide overlay + if (['color', 'date', 'time', 'list', 'combo', 'enum'].indexOf(obj.type) != -1) { + if ($("#w2ui-overlay").length > 0) $('#w2ui-overlay')[0].hide(); + } + if (['int', 'float', 'money', 'currency', 'percent'].indexOf(obj.type) != -1) { + if (val !== '' && !obj.checkType(val)) { + $(obj.el).val('').change(); + if (options.silent === false) { + $(obj.el).w2tag('Not a valid number'); + setTimeout(function () { $(obj.el).w2tag(''); }, 3000); + } + } + } + // date or time + if (['date', 'time'].indexOf(obj.type) != -1) { + if (w2utils.isInt(obj.el.value)) { + $(obj.el).val(w2utils.formatDate(new Date(parseInt(obj.el.value)), options.format)).change(); + } + // check if in range + if (val !== '' && !obj.inRange(obj.el.value)) { + $(obj.el).val('').removeData('selected').change(); + if (options.silent === false) { + $(obj.el).w2tag('Not in range'); + setTimeout(function () { $(obj.el).w2tag(''); }, 3000); + } + } else { + if (obj.type == 'date' && val !== '' && !w2utils.isDate(obj.el.value, options.format)) { + $(obj.el).val('').removeData('selected').change(); + if (options.silent === false) { + $(obj.el).w2tag('Not a valid date'); + setTimeout(function () { $(obj.el).w2tag(''); }, 3000); + } + } + if (obj.type == 'time' && val !== '' && !w2utils.isTime(obj.el.value)) { + $(obj.el).val('').removeData('selected').change(); + if (options.silent === false) { + $(obj.el).w2tag('Not a valid time'); + setTimeout(function () { $(obj.el).w2tag(''); }, 3000); + } + } + } + } + // clear search input + if (obj.type == 'enum') { + $(obj.helpers.multi).find('input').val('').width(20); + } + // file + if (obj.type == 'file') { + $(obj.helpers.multi).css({ 'outline': 'none' }); + } + }, + + keyPress: function (event) { + var obj = this; + var options = obj.options; + // ignore wrong pressed key + if (['int', 'float', 'money', 'currency', 'percent', 'hex', 'color', 'alphanumeric'].indexOf(obj.type) != -1) { + // keyCode & charCode differ in FireFox + if (event.metaKey || event.ctrlKey || event.altKey || (event.charCode != event.keyCode && event.keyCode > 0)) return; + var ch = String.fromCharCode(event.charCode); + if (!obj.checkType(ch, true) && event.keyCode != 13) { + event.preventDefault(); + if (event.stopPropagation) event.stopPropagation(); else event.cancelBubble = true; + return false; + } + } + // update date popup + if (['date', 'time'].indexOf(obj.type) != -1) { + setTimeout(function () { obj.updateOverlay(); }, 1); + } + }, + + keyDown: function (event, extra) { + var obj = this; + var options = obj.options; + var key = event.keyCode || (extra && extra.keyCode); + // numeric + if (['int', 'float', 'money', 'currency', 'percent'].indexOf(obj.type) != -1) { + if (!options.keyboard || $(obj.el).attr('readonly')) return; + var cancel = false; + var val = parseFloat($(obj.el).val().replace(options.moneyRE, '')) || 0; + var inc = options.step; + if (event.ctrlKey || event.metaKey) inc = 10; + switch (key) { + case 38: // up + if (event.shiftKey) break; // no action if shift key is pressed + $(obj.el).val((val + inc <= options.max || options.max === null ? Number((val + inc).toFixed(12)) : options.max)).change(); + cancel = true; + break; + case 40: // down + if (event.shiftKey) break; // no action if shift key is pressed + $(obj.el).val((val - inc >= options.min || options.min === null ? Number((val - inc).toFixed(12)) : options.min)).change(); + cancel = true; + break; + } + if (cancel) { + event.preventDefault(); + setTimeout(function () { + // set cursor to the end + obj.el.setSelectionRange(obj.el.value.length, obj.el.value.length); + }, 0); + } + } + // date + if (obj.type == 'date') { + if (!options.keyboard || $(obj.el).attr('readonly')) return; + var cancel = false; + var daymil = 24*60*60*1000; + var inc = 1; + if (event.ctrlKey || event.metaKey) inc = 10; + var dt = w2utils.isDate($(obj.el).val(), options.format, true); + if (!dt) { dt = new Date(); daymil = 0; } + switch (key) { + case 38: // up + if (event.shiftKey) break; // no action if shift key is pressed + var newDT = w2utils.formatDate(dt.getTime() + daymil, options.format); + if (inc == 10) newDT = w2utils.formatDate(new Date(dt.getFullYear(), dt.getMonth()+1, dt.getDate()), options.format); + $(obj.el).val(newDT).change(); + cancel = true; + break; + case 40: // down + if (event.shiftKey) break; // no action if shift key is pressed + var newDT = w2utils.formatDate(dt.getTime() - daymil, options.format); + if (inc == 10) newDT = w2utils.formatDate(new Date(dt.getFullYear(), dt.getMonth()-1, dt.getDate()), options.format); + $(obj.el).val(newDT).change(); + cancel = true; + break; + } + if (cancel) { + event.preventDefault(); + setTimeout(function () { + // set cursor to the end + obj.el.setSelectionRange(obj.el.value.length, obj.el.value.length); + obj.updateOverlay(); + }, 0); + } + } + // time + if (obj.type == 'time') { + if (!options.keyboard || $(obj.el).attr('readonly')) return; + var cancel = false; + var inc = 1; + if (event.ctrlKey || event.metaKey) inc = 60; + if (w2utils.isInt(obj.el.value)) { + $(obj.el).val(w2utils.formatTime(new Date(parseInt(obj.el.value)), options.format)).change(); + } + var val = $(obj.el).val(); + var time = obj.toMin(val) || obj.toMin((new Date()).getHours() + ':' + ((new Date()).getMinutes() - 1)); + switch (key) { + case 38: // up + if (event.shiftKey) break; // no action if shift key is pressed + time += inc; + cancel = true; + break; + case 40: // down + if (event.shiftKey) break; // no action if shift key is pressed + time -= inc; + cancel = true; + break; + } + if (cancel) { + $(obj.el).val(obj.fromMin(time)).change(); + event.preventDefault(); + setTimeout(function () { + // set cursor to the end + obj.el.setSelectionRange(obj.el.value.length, obj.el.value.length); + }, 0); + } + } + // color + if (obj.type == 'color') { + if ($(obj.el).attr('readonly')) return; + // paste + if (event.keyCode == 86 && (event.ctrlKey || event.metaKey)) { + $(obj.el).prop('maxlength', 7); + setTimeout(function () { + var val = $(obj).val(); + if (val.substr(0, 1) == '#') val = val.substr(1); + if (!w2utils.isHex(val)) val = ''; + $(obj).val(val).prop('maxlength', 6).change(); + }, 20); + } + if ((event.ctrlKey || event.metaKey) && !event.shiftKey) { + if (typeof obj.tmp.cind1 == 'undefined') { + obj.tmp.cind1 = -1; + obj.tmp.cind2 = -1; + } else { + switch (key) { + case 38: // up + obj.tmp.cind1--; + break; + case 40: // down + obj.tmp.cind1++; + break; + case 39: // right + obj.tmp.cind2++; + break; + case 37: // left + obj.tmp.cind2--; + break; + } + if (obj.tmp.cind1 < 0) obj.tmp.cind1 = 0; + if (obj.tmp.cind1 > this.pallete.length - 1) obj.tmp.cind1 = this.pallete.length - 1; + if (obj.tmp.cind2 < 0) obj.tmp.cind2 = 0; + if (obj.tmp.cind2 > this.pallete[0].length - 1) obj.tmp.cind2 = this.pallete[0].length - 1; + } + if ([37, 38, 39, 40].indexOf(key) != -1) { + $(obj.el).val(this.pallete[obj.tmp.cind1][obj.tmp.cind2]).change(); + event.preventDefault(); + } + } + } + // list/select/combo + if (['list', 'combo', 'enum'].indexOf(obj.type) != -1) { + if ($(obj.el).attr('readonly')) return; + var cancel = false; + var selected = $(obj.el).data('selected'); + var focus = $(obj.helpers.focus).find('input'); + if (obj.type == 'list') { + if ([37, 38, 39, 40].indexOf(key) == -1) obj.refresh(); // arrows + } + // apply arrows + switch (key) { + case 27: // escape + if (obj.type == 'list') { + if ($(focus).val() != '') $(focus).val(''); + event.stopPropagation(); // escape in field should not close popup + } + break; + case 37: // left + case 39: // right + // cancel = true; + break; + case 13: // enter + if ($('#w2ui-overlay').length == 0) break; // no action if overlay not open + var item = options.items[options.index]; + var multi = $(obj.helpers.multi).find('input'); + if (obj.type == 'enum') { + if (item != null) { + // trigger event + var eventData = obj.trigger({ phase: 'before', type: 'add', target: obj.el, originalEvent: event.originalEvent, item: item }); + if (eventData.isCancelled === true) return; + item = eventData.item; // need to reassign because it could be recreated by user + // default behavior + if (selected.length >= options.max && options.max > 0) selected.pop(); + delete item.hidden; + delete obj.tmp.force_open; + selected.push(item); + $(obj.el).change(); + multi.val('').width(20); + obj.refresh(); + // event after + obj.trigger($.extend(eventData, { phase: 'after' })); + } else { + // trigger event + item = { id: multi.val(), text: multi.val() } + var eventData = obj.trigger({ phase: 'before', type: 'new', target: obj.el, originalEvent: event.originalEvent, item: item }); + if (eventData.isCancelled === true) return; + item = eventData.item; // need to reassign because it could be recreated by user + // default behavior + if (typeof obj.onNew == 'function') { + if (selected.length >= options.max && options.max > 0) selected.pop(); + delete obj.tmp.force_open; + selected.push(item); + $(obj.el).change(); + multi.val('').width(20); + obj.refresh(); + } + // event after + obj.trigger($.extend(eventData, { phase: 'after' })); + } + } else { + if (item) $(obj.el).data('selected', item).val(item.text).change(); + if ($(obj.el).val() == '' && $(obj.el).data('selected')) $(obj.el).removeData('selected').val('').change(); + if (obj.type == 'list') { + focus.val(''); + obj.refresh(); + } + // hide overlay + obj.tmp.force_hide = true; + } + break; + case 8: // backspace + case 46: // delete + if (obj.type == 'enum' && key == 8) { + if ($(obj.helpers.multi).find('input').val() == '' && selected.length > 0) { + var item = selected[selected.length - 1]; + // trigger event + var eventData = obj.trigger({ phase: 'before', type: 'remove', target: obj.el, originalEvent: event.originalEvent, item: item }); + if (eventData.isCancelled === true) return; + // default behavior + selected.pop(); + $(obj.el).trigger('change'); + obj.refresh(); + // event after + obj.trigger($.extend(eventData, { phase: 'after' })); + } + } + if (obj.type == 'list' && $(focus).val() == '') { + $(obj.el).data('selected', {}).change(); + obj.refresh(); + } + break; + case 38: // up + options.index = w2utils.isInt(options.index) ? parseInt(options.index) : 0; + options.index--; + while (options.index > 0 && options.items[options.index].hidden) options.index--; + if (options.index == 0 && options.items[options.index].hidden) { + while (options.items[options.index] && options.items[options.index].hidden) options.index++; + } + cancel = true; + break; + case 40: // down + options.index = w2utils.isInt(options.index) ? parseInt(options.index) : -1; + options.index++; + while (options.index < options.items.length-1 && options.items[options.index].hidden) options.index++; + if (options.index == options.items.length-1 && options.items[options.index].hidden) { + while (options.items[options.index] && options.items[options.index].hidden) options.index--; + } + // show overlay if not shown + var input = obj.el; + if (['enum'].indexOf(obj.type) != -1) input = obj.helpers.multi.find('input'); + if ($(input).val() == '' && $('#w2ui-overlay').length == 0) { + obj.tmp.force_open = true; + } else { + cancel = true; + } + break; + } + if (cancel) { + if (options.index < 0) options.index = 0; + if (options.index >= options.items.length) options.index = options.items.length -1; + obj.updateOverlay(); + // cancel event + event.preventDefault(); + setTimeout(function () { + // set cursor to the end + if (obj.type == 'enum') { + var tmp = obj.helpers.multi.find('input').get(0); + tmp.setSelectionRange(tmp.value.length, tmp.value.length); + } else if (obj.type == 'list') { + var tmp = obj.helpers.focus.find('input').get(0); + tmp.setSelectionRange(tmp.value.length, tmp.value.length); + } else { + obj.el.setSelectionRange(obj.el.value.length, obj.el.value.length); + } + }, 0); + return; + } + // expand input + if (obj.type == 'enum') { + var input = obj.helpers.multi.find('input'); + var search = input.val(); + input.width(((search.length + 2) * 8) + 'px'); + } + // run search + if ([16, 17, 18, 20, 37, 39, 91].indexOf(key) == -1) { // no refreah on crtl, shift, left/right arrows, etc + setTimeout(function () { + if (!obj.tmp.force_hide) obj.request(); + obj.search(); + }, 1); + } + } + }, + + keyUp: function (event) { + if (this.type == 'color') { + if (event.keyCode == 86 && (event.ctrlKey || event.metaKey)) $(this).prop('maxlength', 6); + } + }, + + clearCache: function () { + var options = this.options; + options.items = []; + this.tmp.xhr_loading = false; + this.tmp.xhr_search = ''; + this.tmp.xhr_total = -1; + this.search(); + }, + + request: function (interval) { + var obj = this; + var options = this.options; + var search = $(obj.el).val() || ''; + // if no url - do nothing + if (!options.url) return; + // -- + if (obj.type == 'enum') { + var tmp = $(obj.helpers.multi).find('input'); + if (tmp.length == 0) search = ''; else search = tmp.val(); + } + if (obj.type == 'list') { + var tmp = $(obj.helpers.focus).find('input'); + if (tmp.length == 0) search = ''; else search = tmp.val(); + } + if (options.minLength != 0 && search.length < options.minLength) { + options.items = []; // need to empty the list + this.updateOverlay(); + return; + } + if (typeof interval == 'undefined') interval = 350; + if (typeof obj.tmp.xhr_search == 'undefined') obj.tmp.xhr_search = ''; + if (typeof obj.tmp.xhr_total == 'undefined') obj.tmp.xhr_total = -1; + // check if need to search + if (options.url && $(obj.el).prop('readonly') != true && ( + (options.items.length === 0 && obj.tmp.xhr_total !== 0) || + (obj.tmp.xhr_total == options.cacheMax && search.length > obj.tmp.xhr_search.length) || + (search.length >= obj.tmp.xhr_search.length && search.substr(0, obj.tmp.xhr_search.length) != obj.tmp.xhr_search) || + (search.length < obj.tmp.xhr_search.length) + )) { + // empty list + obj.tmp.xhr_loading = true; + obj.search(); + // timeout + clearTimeout(obj.tmp.timeout); + obj.tmp.timeout = setTimeout(function () { + // trigger event + var url = options.url; + var postData = { + search : search, + max : options.cacheMax + }; + $.extend(postData, options.postData); + var eventData = obj.trigger({ phase: 'before', type: 'request', target: obj.el, url: url, postData: postData }); + if (eventData.isCancelled === true) return; + url = eventData.url; + postData = eventData.postData; + // console.log('REMOTE SEARCH:', search); + if (obj.tmp.xhr) obj.tmp.xhr.abort(); + var ajaxOptions = { + type : 'GET', + url : url, + data : postData, + dataType : 'JSON' // expected from server + }; + if (w2utils.settings.dataType == 'JSON') { + ajaxOptions.type = 'POST'; + ajaxOptions.data = JSON.stringify(ajaxOptions.data); + ajaxOptions.contentType = 'application/json'; + } + obj.tmp.xhr = $.ajax(ajaxOptions) + .done(function (data, status, xhr) { + // trigger event + var eventData2 = obj.trigger({ phase: 'before', type: 'load', target: obj.el, search: postData.search, data: data, xhr: xhr }); + if (eventData2.isCancelled === true) return; + // default behavior + data = eventData2.data; + if (typeof data == 'string') data = JSON.parse(data); + if (data.status != 'success') { + console.log('ERROR: server did not return proper structure. It should return', { status: 'success', items: [{ id: 1, text: 'item' }] }); + return; + } + // remove all extra items if more then needed for cache + if (data.items.length > options.cacheMax) data.items.splice(options.cacheMax, 100000); + // remember stats + obj.tmp.xhr_loading = false; + obj.tmp.xhr_search = search; + obj.tmp.xhr_total = data.items.length; + options.items = data.items; + if (search == '' && data.items.length == 0) obj.tmp.emptySet = true; else obj.tmp.emptySet = false; + obj.search(); + // console.log('-->', 'retrieved:', obj.tmp.xhr_total); + // event after + obj.trigger($.extend(eventData2, { phase: 'after' })); + }) + .fail(function (xhr, status, error) { + // trigger event + var errorObj = { status: status, error: error, rawResponseText: xhr.responseText }; + var eventData2 = obj.trigger({ phase: 'before', type: 'error', target: obj.el, search: search, error: errorObj, xhr: xhr }); + if (eventData2.isCancelled === true) return; + // default behavior + if (status != 'abort') { + var data; + try { data = $.parseJSON(xhr.responseText) } catch (e) {} + console.log('ERROR: Server communication failed.', + '\n EXPECTED:', { status: 'success', items: [{ id: 1, text: 'item' }] }, + '\n OR:', { status: 'error', message: 'error message' }, + '\n RECEIVED:', typeof data == 'object' ? data : xhr.responseText); + } + // reset stats + obj.clearCache(); + // event after + obj.trigger($.extend(eventData2, { phase: 'after' })); + }); + // event after + obj.trigger($.extend(eventData, { phase: 'after' })); + }, interval); + } + }, + + search: function () { + var obj = this; + var options = this.options; + var search = $(obj.el).val(); + var target = obj.el; + var ids = []; + var selected= $(obj.el).data('selected'); + if (obj.type == 'enum') { + target = $(obj.helpers.multi).find('input'); + search = target.val(); + for (var s in selected) { if (selected[s]) ids.push(selected[s].id); } + } + if (obj.type == 'list') { + target = $(obj.helpers.focus).find('input'); + search = target.val(); + for (var s in selected) { if (selected[s]) ids.push(selected[s].id); } + } + // trigger event + var eventData = obj.trigger({ phase: 'before', type: 'search', target: target, search: search }); + if (eventData.isCancelled === true) return; + if (obj.tmp.xhr_loading !== true) { + var shown = 0; + for (var i in options.items) { + var item = options.items[i]; + var prefix = ''; + var suffix = ''; + if (['is', 'begins'].indexOf(options.match) != -1) prefix = '^'; + if (['is', 'ends'].indexOf(options.match) != -1) suffix = '$'; + try { + var re = new RegExp(prefix + search + suffix, 'i'); + if (re.test(item.text) || item.text == '...') item.hidden = false; else item.hidden = true; + } catch (e) {} + // do not show selected items + if (obj.type == 'enum' && $.inArray(item.id, ids) != -1) item.hidden = true; + if (item.hidden !== true) shown++; + } + if (obj.type != 'combo') { // don't preselect first for combo + options.index = 0; + while (options.items[options.index] && options.items[options.index].hidden) options.index++; + } else { + options.index = -1; + } + if (shown <= 0) options.index = -1; + options.spinner = false; + obj.updateOverlay(); + setTimeout(function () { + var html = $('#w2ui-overlay').html() || ''; + if (options.markSearch && html.indexOf('$.fn.w2menuHandler') != -1) { // do not highlight when no items + $('#w2ui-overlay').w2marker(search); + } + }, 1); + } else { + options.items.splice(0, options.cacheMax); + options.spinner = true; + obj.updateOverlay(); + } + // event after + obj.trigger($.extend(eventData, { phase: 'after' })); + }, + + updateOverlay: function () { + var obj = this; + var options = this.options; + // color + if (this.type == 'color') { + if ($(obj.el).attr('readonly')) return; + if ($('#w2ui-overlay').length == 0) { + $(obj.el).w2overlay(obj.getColorHTML()); + } else { + $('#w2ui-overlay').html(obj.getColorHTML()); + } + // bind events + $('#w2ui-overlay .color') + .on('mousedown', function (event) { + var color = $(event.originalEvent.target).attr('name'); + var index = $(event.originalEvent.target).attr('index').split(':'); + obj.tmp.cind1 = index[0]; + obj.tmp.cind2 = index[1]; + $(obj.el).val(color).change(); + $(this).html('•'); + }) + .on('mouseup', function () { + setTimeout(function () { + if ($("#w2ui-overlay").length > 0) $('#w2ui-overlay').removeData('keepOpen')[0].hide(); + }, 10); + }); + } + // date + if (this.type == 'date') { + if ($(obj.el).attr('readonly')) return; + if ($('#w2ui-overlay').length == 0) { + $(obj.el).w2overlay('<div class="w2ui-reset w2ui-calendar" onclick="event.stopPropagation();"></div>', { + css: { "background-color": "#f5f5f5" } + }); + } + var month, year; + var dt = w2utils.isDate($(obj.el).val(), obj.options.format, true); + if (dt) { month = dt.getMonth() + 1; year = dt.getFullYear(); } + (function refreshCalendar(month, year) { + $('#w2ui-overlay > div > div').html(obj.getMonthHTML(month, year)); + $('#w2ui-overlay .w2ui-calendar-title') + .on('mousedown', function () { + if ($(this).next().hasClass('w2ui-calendar-jump')) { + $(this).next().remove(); + } else { + var selYear, selMonth; + $(this).after('<div class="w2ui-calendar-jump" style=""></div>'); + $(this).next().hide().html(obj.getYearHTML()).fadeIn(200); + setTimeout(function () { + $('#w2ui-overlay .w2ui-calendar-jump') + .find('.w2ui-jump-month, .w2ui-jump-year') + .on('click', function () { + if ($(this).hasClass('w2ui-jump-month')) { + $(this).parent().find('.w2ui-jump-month').removeClass('selected'); + $(this).addClass('selected'); + selMonth = $(this).attr('name'); + } + if ($(this).hasClass('w2ui-jump-year')) { + $(this).parent().find('.w2ui-jump-year').removeClass('selected'); + $(this).addClass('selected'); + selYear = $(this).attr('name'); + } + if (selYear != null && selMonth != null) { + $('#w2ui-overlay .w2ui-calendar-jump').fadeOut(100); + setTimeout(function () { refreshCalendar(parseInt(selMonth)+1, selYear); }, 100); + } + }); + $('#w2ui-overlay .w2ui-calendar-jump >:last-child').prop('scrollTop', 2000); + }, 1); + } + }); + $('#w2ui-overlay .w2ui-date') + .on('mousedown', function () { + var day = $(this).attr('date'); + $(obj.el).val(day).change(); + $(this).css({ 'background-color': '#B6D5FB', 'border-color': '#aaa' }); + }) + .on('mouseup', function () { + setTimeout(function () { + if ($("#w2ui-overlay").length > 0) $('#w2ui-overlay').removeData('keepOpen')[0].hide(); + }, 10); + }); + $('#w2ui-overlay .previous').on('mousedown', function () { + var tmp = obj.options.current.split('/'); + tmp[0] = parseInt(tmp[0]) - 1; + refreshCalendar(tmp[0], tmp[1]); + }); + $('#w2ui-overlay .next').on('mousedown', function () { + var tmp = obj.options.current.split('/'); + tmp[0] = parseInt(tmp[0]) + 1; + refreshCalendar(tmp[0], tmp[1]); + }); + }) (month, year); + } + // date + if (this.type == 'time') { + if ($(obj.el).attr('readonly')) return; + if ($('#w2ui-overlay').length == 0) { + $(obj.el).w2overlay('<div class="w2ui-reset w2ui-calendar-time" onclick="event.stopPropagation();"></div>', { + css: { "background-color": "#fff" } + }); + } + var h24 = (this.options.format == 'h24' ? true : false); + $('#w2ui-overlay > div').html(obj.getHourHTML()); + $('#w2ui-overlay .w2ui-time') + .on('mousedown', function (event) { + $(this).css({ 'background-color': '#B6D5FB', 'border-color': '#aaa' }); + var hour = $(this).attr('hour'); + $(obj.el).val((hour > 12 && !h24 ? hour - 12 : hour) + ':00' + (!h24 ? (hour < 12 ? ' am' : ' pm') : '')).change(); + }) + .on('mouseup', function () { + var hour = $(this).attr('hour'); + if ($("#w2ui-overlay").length > 0) $('#w2ui-overlay')[0].hide(); + $(obj.el).w2overlay('<div class="w2ui-reset w2ui-calendar-time"></div>', { css: { "background-color": "#fff" } }); + $('#w2ui-overlay > div').html(obj.getMinHTML(hour)); + $('#w2ui-overlay .w2ui-time') + .on('mousedown', function () { + $(this).css({ 'background-color': '#B6D5FB', 'border-color': '#aaa' }); + var min = $(this).attr('min'); + $(obj.el).val((hour > 12 && !h24 ? hour - 12 : hour) + ':' + (min < 10 ? 0 : '') + min + (!h24 ? (hour < 12 ? ' am' : ' pm') : '')).change(); + }) + .on('mouseup', function () { + setTimeout(function () { if ($("#w2ui-overlay").length > 0) $('#w2ui-overlay').removeData('keepOpen')[0].hide(); }, 10); + }); + }); + } + // list + if (['list', 'combo', 'enum'].indexOf(this.type) != -1) { + var el = this.el; + var input = this.el; + if (this.type == 'enum') { + el = $(this.helpers.multi); + input = $(el).find('input'); + } + if (this.type == 'list') { + input = $(this.helpers.focus).find('input'); + } + if ($(input).is(':focus')) { + if (options.openOnFocus === false && $(input).val() == '' && obj.tmp.force_open !== true) { + $().w2overlay(); + return; + } + if (obj.tmp.force_hide) { + $().w2overlay(); + setTimeout(function () { + delete obj.tmp.force_hide; + }, 1); + return; + } + if ($(input).val() != '') delete obj.tmp.force_open; + if ($('#w2ui-overlay').length == 0) options.index = 0; + var msgNoItems = w2utils.lang('No matches'); + if (options.url != null && $(input).val().length < options.minLength && obj.tmp.emptySet !== true) msgNoItems = options.minLength + ' ' + w2utils.lang('letters or more...'); + if (options.url != null && $(input).val() == '' && obj.tmp.emptySet !== true) msgNoItems = w2utils.lang('Type to search....'); + $(el).w2menu('refresh', $.extend(true, {}, options, { + search : false, + render : options.renderDrop, + maxHeight : options.maxDropHeight, + msgNoItems : msgNoItems, + // selected with mouse + onSelect: function (event) { + if (obj.type == 'enum') { + var selected = $(obj.el).data('selected'); + if (event.item) { + // trigger event + var eventData = obj.trigger({ phase: 'before', type: 'add', target: obj.el, originalEvent: event.originalEvent, item: event.item }); + if (eventData.isCancelled === true) return; + // default behavior + if (selected.length >= options.max && options.max > 0) selected.pop(); + delete event.item.hidden; + selected.push(event.item); + $(obj.el).data('selected', selected).change(); + $(obj.helpers.multi).find('input').val('').width(20); + obj.refresh(); + if ($("#w2ui-overlay").length > 0) $('#w2ui-overlay')[0].hide(); + // event after + obj.trigger($.extend(eventData, { phase: 'after' })); + } + } else { + $(obj.el).data('selected', event.item).val(event.item.text).change(); + if (obj.helpers.focus) { + obj.helpers.focus.find('input').val(''); + obj.refresh(); + } + } + } + })); + } + } + }, + + inRange: function (str) { + var inRange = false; + if (this.type == 'date') { + var dt = w2utils.isDate(str, this.options.format, true); + if (dt) { + // enable range + if (this.options.start || this.options.end) { + var st = (typeof this.options.start == 'string' ? this.options.start : $(this.options.start).val()); + var en = (typeof this.options.end == 'string' ? this.options.end : $(this.options.end).val()); + var start = w2utils.isDate(st, this.options.format, true); + var end = w2utils.isDate(en, this.options.format, true); + var current = new Date(dt); + if (!start) start = current; + if (!end) end = current; + if (current >= start && current <= end) inRange = true; + } else { + inRange = true; + } + // block predefined dates + if (this.options.blocked && $.inArray(str, this.options.blocked) != -1) inRange = false; + } + } + if (this.type == 'time') { + if (this.options.start || this.options.end) { + var tm = this.toMin(str); + var tm1 = this.toMin(this.options.start); + var tm2 = this.toMin(this.options.end); + if (!tm1) tm1 = tm; + if (!tm2) tm2 = tm; + if (tm >= tm1 && tm <= tm2) inRange = true; + } else { + inRange = true; + } + } + return inRange; + }, + + /* + * INTERNAL FUNCTIONS + */ + + checkType: function (ch, loose) { + var obj = this; + switch (obj.type) { + case 'int': + if (loose && ['-'].indexOf(ch) != -1) return true; + return w2utils.isInt(ch.replace(obj.options.numberRE, '')); + case 'percent': + ch = ch.replace(/%/g, ''); + case 'float': + if (loose && ['-','.'].indexOf(ch) != -1) return true; + return w2utils.isFloat(ch.replace(obj.options.numberRE, '')); + case 'money': + case 'currency': + if (loose && ['-', '.', obj.options.groupSymbol, obj.options.currencyPrefix, obj.options.currencySuffix].indexOf(ch) != -1) return true; + return w2utils.isFloat(ch.replace(obj.options.moneyRE, '')); + case 'hex': + case 'color': + return w2utils.isHex(ch); + case 'alphanumeric': + return w2utils.isAlphaNumeric(ch); + } + return true; + }, + + addPrefix: function () { + var obj = this; + setTimeout(function () { + if (obj.type === 'clear') return; + var helper; + var tmp = $(obj.el).data('tmp') || {}; + if (tmp['old-padding-left']) $(obj.el).css('padding-left', tmp['old-padding-left']); + tmp['old-padding-left'] = $(obj.el).css('padding-left'); + $(obj.el).data('tmp', tmp); + // remove if already displaed + if (obj.helpers.prefix) $(obj.helpers.prefix).remove(); + if (obj.options.prefix !== '') { + // add fresh + $(obj.el).before( + '<div class="w2ui-field-helper">'+ + obj.options.prefix + + '</div>' + ); + helper = $(obj.el).prev(); + helper + .css({ + 'color' : $(obj.el).css('color'), + 'font-family' : $(obj.el).css('font-family'), + 'font-size' : $(obj.el).css('font-size'), + 'padding-top' : $(obj.el).css('padding-top'), + 'padding-bottom' : $(obj.el).css('padding-bottom'), + 'padding-left' : $(obj.el).css('padding-left'), + 'padding-right' : 0, + 'margin-top' : (parseInt($(obj.el).css('margin-top'), 10) + 2) + 'px', + 'margin-bottom' : (parseInt($(obj.el).css('margin-bottom'), 10) + 1) + 'px', + 'margin-left' : $(obj.el).css('margin-left'), + 'margin-right' : 0 + }) + .on('click', function (event) { + if (obj.options.icon && typeof obj.onIconClick == 'function') { + // event before + var eventData = obj.trigger({ phase: 'before', type: 'iconClick', target: obj.el, el: $(this).find('span.w2ui-icon')[0] }); + if (eventData.isCancelled === true) return; + + // intentionally empty + + // event after + obj.trigger($.extend(eventData, { phase: 'after' })); + } else { + if (obj.type == 'list') { + $(obj.helpers.focus).find('input').focus(); + } else { + $(obj.el).focus(); + } + } + }); + $(obj.el).css('padding-left', (helper.width() + parseInt($(obj.el).css('padding-left'), 10)) + 'px'); + // remember helper + obj.helpers.prefix = helper; + } + }, 1); + }, + + addSuffix: function () { + var obj = this; + var helper, pr; + setTimeout(function () { + if (obj.type === 'clear') return; + var tmp = $(obj.el).data('tmp') || {}; + if (tmp['old-padding-right']) $(obj.el).css('padding-right', tmp['old-padding-right']); + tmp['old-padding-right'] = $(obj.el).css('padding-right'); + $(obj.el).data('tmp', tmp); + pr = parseInt($(obj.el).css('padding-right'), 10); + if (obj.options.arrows) { + // remove if already displaed + if (obj.helpers.arrows) $(obj.helpers.arrows).remove(); + // add fresh + $(obj.el).after( + '<div class="w2ui-field-helper" style="border: 1px solid transparent"> '+ + ' <div class="w2ui-field-up" type="up">'+ + ' <div class="arrow-up" type="up"></div>'+ + ' </div>'+ + ' <div class="w2ui-field-down" type="down">'+ + ' <div class="arrow-down" type="down"></div>'+ + ' </div>'+ + '</div>'); + var height = w2utils.getSize(obj.el, 'height'); + helper = $(obj.el).next(); + helper.css({ + 'color' : $(obj.el).css('color'), + 'font-family' : $(obj.el).css('font-family'), + 'font-size' : $(obj.el).css('font-size'), + 'height' : ($(obj.el).height() + parseInt($(obj.el).css('padding-top'), 10) + parseInt($(obj.el).css('padding-bottom'), 10) ) + 'px', + 'padding' : 0, + 'margin-top' : (parseInt($(obj.el).css('margin-top'), 10) + 1) + 'px', + 'margin-bottom' : 0, + 'border-left' : '1px solid silver' + }) + .css('margin-left', '-'+ (helper.width() + parseInt($(obj.el).css('margin-right'), 10) + 12) + 'px') + .on('mousedown', function (event) { + $('body').on('mouseup', tmp); + $('body').data('_field_update_timer', setTimeout(update, 700)); + update(false); + // timer function + function tmp() { + clearTimeout($('body').data('_field_update_timer')); + $('body').off('mouseup', tmp); + } + // update function + function update(notimer) { + $(obj.el).focus(); + obj.keyDown($.Event("keydown"), { + keyCode : ($(event.target).attr('type') == 'up' ? 38 : 40) + }); + if (notimer !== false) $('body').data('_field_update_timer', setTimeout(update, 60)); + } + }); + pr += helper.width() + 12; + $(obj.el).css('padding-right', pr + 'px'); + // remember helper + obj.helpers.arrows = helper; + } + if (obj.options.suffix !== '') { + // remove if already displaed + if (obj.helpers.suffix) $(obj.helpers.suffix).remove(); + // add fresh + $(obj.el).after( + '<div class="w2ui-field-helper">'+ + obj.options.suffix + + '</div>'); + helper = $(obj.el).next(); + helper + .css({ + 'color' : $(obj.el).css('color'), + 'font-family' : $(obj.el).css('font-family'), + 'font-size' : $(obj.el).css('font-size'), + 'padding-top' : $(obj.el).css('padding-top'), + 'padding-bottom' : $(obj.el).css('padding-bottom'), + 'padding-left' : '3px', + 'padding-right' : $(obj.el).css('padding-right'), + 'margin-top' : (parseInt($(obj.el).css('margin-top'), 10) + 2) + 'px', + 'margin-bottom' : (parseInt($(obj.el).css('margin-bottom'), 10) + 1) + 'px' + }) + .on('click', function (event) { + if (obj.type == 'list') { + $(obj.helpers.focus).find('input').focus(); + } else { + $(obj.el).focus(); + } + }); + + helper.css('margin-left', '-'+ (w2utils.getSize(helper, 'width') + parseInt($(obj.el).css('margin-right'), 10) + 2) + 'px'); + pr += helper.width() + 3; + $(obj.el).css('padding-right', pr + 'px'); + // remember helper + obj.helpers.suffix = helper; + } + }, 1); + }, + + addFocus: function () { + var obj = this; + var options = this.options; + var width = 0; // 11 - show search icon, 0 do not show + // clean up & init + $(obj.helpers.focus).remove(); + // build helper + var html = + '<div class="w2ui-field-helper">'+ + ' <div class="w2ui-icon icon-search"></div>'+ + ' <input type="text" autocomplete="off">'+ + '<div>'; + $(obj.el).attr('tabindex', -1).before(html); + var helper = $(obj.el).prev(); + obj.helpers.focus = helper; + helper.css({ + width : $(obj.el).width(), + "margin-top" : $(obj.el).css('margin-top'), + "margin-left" : (parseInt($(obj.el).css('margin-left')) + parseInt($(obj.el).css('padding-left'))) + 'px', + "margin-bottom" : $(obj.el).css('margin-bottom'), + "margin-right" : $(obj.el).css('margin-right') + }) + .find('input') + .css({ + cursor : 'default', + width : '100%', + outline : 'none', + opacity : 1, + margin : 0, + border : '1px solid transparent', + padding : $(obj.el).css('padding-top'), + "padding-left" : 0, + "margin-left" : (width > 0 ? width + 6 : 0), + "background-color" : 'transparent' + }); + // INPUT events + helper.find('input') + .on('click', function (event) { + if ($('#w2ui-overlay').length == 0) obj.focus(event); + event.stopPropagation(); + }) + .on('focus', function (event) { + $(obj.el).css({ 'outline': 'auto 5px #7DB4F3', 'outline-offset': '-2px' }); + $(this).val(''); + $(obj.el).triggerHandler('focus'); + if (event.stopPropagation) event.stopPropagation(); else event.cancelBubble = true; + }) + .on('blur', function (event) { + $(obj.el).css('outline', 'none'); + $(this).val(''); + obj.refresh(); + $(obj.el).triggerHandler('blur'); + if (event.stopPropagation) event.stopPropagation(); else event.cancelBubble = true; + }) + .on('keyup', function (event) { obj.keyUp(event) }) + .on('keydown', function (event) { obj.keyDown(event) }) + .on('keypress', function (event) { obj.keyPress(event); }); + // MAIN div + helper.on('click', function (event) { $(this).find('input').focus(); }); + obj.refresh(); + }, + + addMulti: function () { + var obj = this; + var options = this.options; + // clean up & init + $(obj.helpers.multi).remove(); + // build helper + var html = ''; + var margin = + 'margin-top : 0px; ' + + 'margin-bottom : 0px; ' + + 'margin-left : ' + $(obj.el).css('margin-left') + '; ' + + 'margin-right : ' + $(obj.el).css('margin-right') + '; '+ + 'width : ' + (w2utils.getSize(obj.el, 'width') + - parseInt($(obj.el).css('margin-left'), 10) + - parseInt($(obj.el).css('margin-right'), 10)) + + 'px;'; + if (obj.type == 'enum') { + html = '<div class="w2ui-field-helper w2ui-list" style="'+ margin + '; box-sizing: border-box">'+ + ' <div style="padding: 0px; margin: 0px; margin-right: 20px; display: inline-block">'+ + ' <ul>'+ + ' <li style="padding-left: 0px; padding-right: 0px" class="nomouse">'+ + ' <input type="text" style="width: 20px" autocomplete="off" '+ ($(obj.el).attr('readonly') ? 'readonly': '') + '>'+ + ' </li>' + ' </ul>'+ + ' </div>'+ + '</div>'; + } + if (obj.type == 'file') { + html = '<div class="w2ui-field-helper w2ui-list" style="'+ margin + '; box-sizing: border-box">'+ + ' <div style="padding: 0px; margin: 0px; margin-right: 20px; display: inline-block">'+ + ' <ul><li style="padding-left: 0px; padding-right: 0px" class="nomouse"></li></ul>'+ + ' <input class="file-input" type="file" name="attachment" multiple style="display: none" tabindex="-1">' + ' </div>'+ + '</div>'; + } + $(obj.el) + .before(html) + .css({ + 'background-color' : 'transparent', + 'border-color' : 'transparent' + }); + + var div = $(obj.el).prev(); + obj.helpers.multi = div; + if (obj.type == 'enum') { + $(obj.el).attr('tabindex', -1); + // INPUT events + div.find('input') + .on('click', function (event) { + if ($('#w2ui-overlay').length == 0) obj.focus(event); + $(obj.el).triggerHandler('click'); + }) + .on('focus', function (event) { + $(div).css({ 'outline': 'auto 5px #7DB4F3', 'outline-offset': '-2px' }); + $(obj.el).triggerHandler('focus'); + if (event.stopPropagation) event.stopPropagation(); else event.cancelBubble = true; + }) + .on('blur', function (event) { + $(div).css('outline', 'none'); + $(obj.el).triggerHandler('blur'); + if (event.stopPropagation) event.stopPropagation(); else event.cancelBubble = true; + }) + .on('keyup', function (event) { obj.keyUp(event) }) + .on('keydown', function (event) { obj.keyDown(event) }) + .on('keypress', function (event) { div.find('.w2ui-enum-placeholder').remove(); obj.keyPress(event); }); + // MAIN div + div.on('click', function (event) { $(this).find('input').focus(); }); + } + if (obj.type == 'file') { + $(obj.el).css('outline', 'none'); + div.on('click', function (event) { + $(obj.el).focus(); + if ($(obj.el).attr('readonly')) return; + obj.blur(event); + div.find('input').click(); + }) + .on('dragenter', function (event) { + if ($(obj.el).attr('readonly')) return; + $(div).addClass('w2ui-file-dragover'); + }) + .on('dragleave', function (event) { + if ($(obj.el).attr('readonly')) return; + var tmp = $(event.target).parents('.w2ui-field-helper'); + if (tmp.length == 0) $(div).removeClass('w2ui-file-dragover'); + }) + .on('drop', function (event) { + if ($(obj.el).attr('readonly')) return; + $(div).removeClass('w2ui-file-dragover'); + var files = event.originalEvent.dataTransfer.files; + for (var i=0, l=files.length; i<l; i++) obj.addFile.call(obj, files[i]); + // cancel to stop browser behaviour + event.preventDefault(); + event.stopPropagation(); + }) + .on('dragover', function (event) { + // cancel to stop browser behaviour + event.preventDefault(); + event.stopPropagation(); + }); + div.find('input') + .on('click', function (event) { + event.stopPropagation(); + }) + .on('change', function () { + if (typeof this.files !== "undefined") { + for (var i = 0, l = this.files.length; i < l; i++) { + obj.addFile.call(obj, this.files[i]); + } + } + }); + } + obj.refresh(); + }, + + addFile: function (file) { + var obj = this; + var options = this.options; + var selected = $(obj.el).data('selected'); + var newItem = { + name : file.name, + type : file.type, + modified : file.lastModifiedDate, + size : file.size, + content : null + }; + var size = 0; + var cnt = 0; + var err; + for (var s in selected) { size += selected[s].size; cnt++; } + // trigger event + var eventData = obj.trigger({ phase: 'before', type: 'add', target: obj.el, file: newItem, total: cnt, totalSize: size }); + if (eventData.isCancelled === true) return; + // check params + if (options.maxFileSize !== 0 && newItem.size > options.maxFileSize) { + err = 'Maximum file size is '+ w2utils.size(options.maxFileSize); + if (options.silent === false) $(obj.el).w2tag(err); + console.log('ERROR: '+ err); + return; + } + if (options.maxSize !== 0 && size + newItem.size > options.maxSize) { + err = 'Maximum total size is '+ w2utils.size(options.maxSize); + if (options.silent === false) $(obj.el).w2tag(err); + console.log('ERROR: '+ err); + return; + } + if (options.max !== 0 && cnt >= options.max) { + err = 'Maximum number of files is '+ options.max; + if (options.silent === false) $(obj.el).w2tag(err); + console.log('ERROR: '+ err); + return; + } + selected.push(newItem); + // read file as base64 + if (typeof FileReader !== "undefined") { + var reader = new FileReader(); + // need a closure + reader.onload = (function () { + return function (event) { + var fl = event.target.result; + var ind = fl.indexOf(','); + newItem.content = fl.substr(ind+1); + obj.refresh(); + $(obj.el).trigger('change'); + // event after + obj.trigger($.extend(eventData, { phase: 'after' })); + }; + })(); + reader.readAsDataURL(file); + } else { + obj.refresh(); + $(obj.el).trigger('change'); + } + }, + + normMenu: function (menu) { + if ($.isArray(menu)) { + for (var m = 0; m < menu.length; m++) { + if (typeof menu[m] == 'string') { + menu[m] = { id: menu[m], text: menu[m] }; + } else { + if (typeof menu[m].text != 'undefined' && typeof menu[m].id == 'undefined') menu[m].id = menu[m].text; + if (typeof menu[m].text == 'undefined' && typeof menu[m].id != 'undefined') menu[m].text = menu[m].id; + if (typeof menu[m].caption != 'undefined') menu[m].text = menu[m].caption; + } + } + return menu; + } else if (typeof menu == 'object') { + var tmp = [] + for (var m in menu) tmp.push({ id: m, text: menu[m] }); + return tmp; + } + }, + + getColorHTML: function () { + var html = '<div class="w2ui-color">'+ + '<table cellspacing="5">'; + for (var i = 0; i < 8; i++) { + html += '<tr>'; + for (var j = 0; j < 8; j++) { + html += '<td>'+ + ' <div class="color" style="background-color: #'+ this.pallete[i][j] +';" name="'+ this.pallete[i][j] +'" index="'+ i + ':' + j +'">'+ + ' '+ ($(this.el).val() == this.pallete[i][j] ? '•' : ' ')+ + ' </div>'+ + '</td>'; + } + html += '</tr>'; + if (i < 2) html += '<tr><td style="height: 8px" colspan="8"></td></tr>'; + } + html += '</table></div>'; + return html; + }, + + getMonthHTML: function (month, year) { + var td = new Date(); + var months = w2utils.settings.fullmonths; + var days = w2utils.settings.fulldays; + var daysCount = ['31', '28', '31', '30', '31', '30', '31', '31', '30', '31', '30', '31']; + var today = td.getFullYear() + '/' + (Number(td.getMonth()) + 1) + '/' + td.getDate(); + // normalize date + year = w2utils.isInt(year) ? parseInt(year) : td.getFullYear(); + month = w2utils.isInt(month) ? parseInt(month) : td.getMonth() + 1; + if (month > 12) { month -= 12; year++; } + if (month < 1 || month === 0) { month += 12; year--; } + if (year/4 == Math.floor(year/4)) { daysCount[1] = '29'; } else { daysCount[1] = '28'; } + this.options.current = month + '/' + year; + + // start with the required date + td = new Date(year, month-1, 1); + var weekDay = td.getDay(); + var tabDays = w2utils.settings.shortdays; + var dayTitle = ''; + for ( var i = 0, len = tabDays.length; i < len; i++) { + dayTitle += '<td>' + tabDays[i] + '</td>'; + } + var html = + '<div class="w2ui-calendar-title title">'+ + ' <div class="w2ui-calendar-previous previous"> <div></div> </div>'+ + ' <div class="w2ui-calendar-next next"> <div></div> </div> '+ + months[month-1] +', '+ year + + '</div>'+ + '<table class="w2ui-calendar-days" cellspacing="0">'+ + ' <tr class="w2ui-day-title">' + dayTitle + '</tr>'+ + ' <tr>'; + + var day = 1; + for (var ci=1; ci<43; ci++) { + if (weekDay === 0 && ci == 1) { + for (var ti=0; ti<6; ti++) html += '<td class="w2ui-day-empty"> </td>'; + ci += 6; + } else { + if (ci < weekDay || day > daysCount[month-1]) { + html += '<td class="w2ui-day-empty"> </td>'; + if ((ci) % 7 === 0) html += '</tr><tr>'; + continue; + } + } + var dt = year + '/' + month + '/' + day; + + var className = ''; + if (ci % 7 == 6) className = ' w2ui-saturday'; + if (ci % 7 === 0) className = ' w2ui-sunday'; + if (dt == today) className += ' w2ui-today'; + + var dspDay = day; + var col = ''; + var bgcol = ''; + var tmp_dt = w2utils.formatDate(dt, this.options.format); + if (this.options.colored && this.options.colored[tmp_dt] !== undefined) { // if there is predefined colors for dates + tmp = this.options.colored[tmp_dt].split(':'); + bgcol = 'background-color: ' + tmp[0] + ';'; + col = 'color: ' + tmp[1] + ';'; + } + html += '<td class="'+ (this.inRange(tmp_dt) ? 'w2ui-date ' : 'w2ui-blocked') + className + '" style="'+ col + bgcol + '" date="'+ tmp_dt +'">'+ + dspDay + + '</td>'; + if (ci % 7 === 0 || (weekDay === 0 && ci == 1)) html += '</tr><tr>'; + day++; + } + html += '</tr></table>'; + return html; + }, + + getYearHTML: function () { + var months = w2utils.settings.shortmonths; + var mhtml = ''; + var yhtml = ''; + for (var m in months) { + mhtml += '<div class="w2ui-jump-month" name="'+ m +'">'+ months[m] + '</div>'; + } + for (var y = 1950; y <= 2020; y++) { + yhtml += '<div class="w2ui-jump-year" name="'+ y +'">'+ y + '</div>' + } + return '<div>'+ mhtml +'</div><div>'+ yhtml +'</div>'; + }, + + getHourHTML: function () { + var tmp = []; + var h24 = (this.options.format == 'h24' ? true : false); + for (var a=0; a<24; a++) { + var time = (a >= 12 && !h24 ? a - 12 : a) + ':00' + (!h24 ? (a < 12 ? ' am' : ' pm') : ''); + if (a == 12 && !h24) time = '12:00 pm'; + if (!tmp[Math.floor(a/8)]) tmp[Math.floor(a/8)] = ''; + var tm1 = this.fromMin(this.toMin(time)); + var tm2 = this.fromMin(this.toMin(time) + 59); + tmp[Math.floor(a/8)] += '<div class="'+ (this.inRange(tm1) || this.inRange(tm2) ? 'w2ui-time ' : 'w2ui-blocked') + '" hour="'+ a +'">'+ time +'</div>'; + } + var html = + '<div class="w2ui-calendar-time"><table><tr>'+ + ' <td>'+ tmp[0] +'</td>' + + ' <td>'+ tmp[1] +'</td>' + + ' <td>'+ tmp[2] +'</td>' + + '</tr></table></div>'; + return html; + }, + + getMinHTML: function (hour) { + if (typeof hour == 'undefined') hour = 0; + var h24 = (this.options.format == 'h24' ? true : false); + var tmp = []; + for (var a=0; a<60; a+=5) { + var time = (hour > 12 && !h24 ? hour - 12 : hour) + ':' + (a < 10 ? 0 : '') + a + ' ' + (!h24 ? (hour < 12 ? 'am' : 'pm') : ''); + var ind = a < 20 ? 0 : (a < 40 ? 1 : 2); + if (!tmp[ind]) tmp[ind] = ''; + tmp[ind] += '<div class="'+ (this.inRange(time) ? 'w2ui-time ' : 'w2ui-blocked') + '" min="'+ a +'">'+ time +'</div>'; + } + var html = + '<div class="w2ui-calendar-time"><table><tr>'+ + ' <td>'+ tmp[0] +'</td>' + + ' <td>'+ tmp[1] +'</td>' + + ' <td>'+ tmp[2] +'</td>' + + '</tr></table></div>'; + return html; + }, + + toMin: function (str) { + if (typeof str != 'string') return null; + var tmp = str.split(':'); + if (tmp.length == 2) { + tmp[0] = parseInt(tmp[0]); + tmp[1] = parseInt(tmp[1]); + if (str.indexOf('pm') != -1 && tmp[0] != 12) tmp[0] += 12; + } else { + return null; + } + return tmp[0] * 60 + tmp[1]; + }, + + fromMin: function (time) { + var ret = ''; + if (time >= 24 * 60) time = time % (24 * 60); + if (time < 0) time = 24 * 60 + time; + var hour = Math.floor(time/60); + var min = ((time % 60) < 10 ? '0' : '') + (time % 60); + if (this.options.format.indexOf('h24') != -1) { + ret = hour + ':' + min; + } else { + ret = (hour <= 12 ? hour : hour - 12) + ':' + min + ' ' + (hour >= 12 ? 'pm' : 'am'); + } + return ret; + } + } + + $.extend(w2field.prototype, w2utils.event); + w2obj.field = w2field; + +}) (jQuery); + +/************************************************************************ +* Library: Web 2.0 UI for jQuery (using prototypical inheritance) +* - Following objects defined +* - w2form - form widget +* - $().w2form - jQuery wrapper +* - Dependencies: jQuery, w2utils, w2fields, w2tabs, w2toolbar, w2alert +* +* == NICE TO HAVE == +* - refresh(field) - would refresh only one field +* - include delta on save +* - create an example how to do cascadic dropdown +* - form should read <select> <options> into items +* - two way data bindings +* - verify validation of fields +* - when field is blank, set record.field = null +* - show/hide a field +* - added getChanges() - not complete +* +************************************************************************/ + + +(function () { + var w2form = function(options) { + // public properties + this.name = null; + this.header = ''; + this.box = null; // HTML element that hold this element + this.url = ''; + this.routeData = {}; // data for dynamic routes + this.formURL = ''; // url where to get form HTML + this.formHTML = ''; // form HTML (might be loaded from the url) + this.page = 0; // current page + this.recid = 0; // can be null or 0 + this.fields = []; + this.actions = {}; + this.record = {}; + this.original = {}; + this.postData = {}; + this.toolbar = {}; // if not empty, then it is toolbar + this.tabs = {}; // if not empty, then it is tabs object + + this.style = ''; + this.focus = 0; // focus first or other element + this.msgNotJSON = w2utils.lang('Return data is not in JSON format.'); + this.msgAJAXerror = w2utils.lang('AJAX error. See console for more details.'); + this.msgRefresh = w2utils.lang('Refreshing...'); + this.msgSaving = w2utils.lang('Saving...'); + + // events + this.onRequest = null; + this.onLoad = null; + this.onValidate = null; + this.onSubmit = null; + this.onSave = null; + this.onChange = null; + this.onRender = null; + this.onRefresh = null; + this.onResize = null; + this.onDestroy = null; + this.onAction = null; + this.onToolbar = null; + this.onError = null; + + // internal + this.isGenerated = false; + this.last = { + xhr: null // jquery xhr requests + } + + $.extend(true, this, w2obj.form, options); + }; + + // ==================================================== + // -- Registers as a jQuery plugin + + $.fn.w2form = function(method) { + if (typeof method === 'object' || !method ) { + var obj = this; + // check name parameter + if (!w2utils.checkName(method, 'w2form')) return; + // remember items + var record = method.record; + var original = method.original; + var fields = method.fields; + var toolbar = method.toolbar; + var tabs = method.tabs; + // extend items + var object = new w2form(method); + $.extend(object, { record: {}, original: {}, fields: [], tabs: {}, toolbar: {}, handlers: [] }); + if ($.isArray(tabs)) { + $.extend(true, object.tabs, { tabs: [] }); + for (var t in tabs) { + var tmp = tabs[t]; + if (typeof tmp === 'object') object.tabs.tabs.push(tmp); else object.tabs.tabs.push({ id: tmp, caption: tmp }); + } + } else { + $.extend(true, object.tabs, tabs); + } + $.extend(true, object.toolbar, toolbar); + // reassign variables + for (var p in fields) { + var field = $.extend(true, {}, fields[p]); + if (typeof field.name == 'undefined' && typeof field.field != 'undefined') field.name = field.field; + if (typeof field.field == 'undefined' && typeof field.name != 'undefined') field.field = field.name; + object.fields[p] = field; + } + for (var p in record) { + if ($.isPlainObject(record[p])) { + object.record[p] = $.extend(true, {}, record[p]); + } else { + object.record[p] = record[p]; + } + } + for (var p in original) { + if ($.isPlainObject(original[p])) { + object.original[p] = $.extend(true, {}, original[p]); + } else { + object.original[p] = original[p]; + } + } + if (obj.length > 0) object.box = obj[0]; + // render if necessary + if (object.formURL != '') { + $.get(object.formURL, function (data) { // should always be $.get as it is template + object.formHTML = data; + object.isGenerated = true; + if ($(object.box).length != 0 || data.length != 0) { + $(object.box).html(data); + object.render(object.box); + } + }); + } else if (object.formHTML != '') { + // it is already loaded into formHTML + } else if ($(this).length != 0 && $.trim($(this).html()) != '') { + object.formHTML = $(this).html(); + } else { // try to generate it + object.formHTML = object.generateHTML(); + } + // register new object + w2ui[object.name] = object; + // render if not loaded from url + if (object.formURL == '') { + if (String(object.formHTML).indexOf('w2ui-page') == -1) { + object.formHTML = '<div class="w2ui-page page-0">'+ object.formHTML +'</div>'; + } + $(object.box).html(object.formHTML); + object.isGenerated = true; + object.render(object.box); + } + return object; + + } else if (w2ui[$(this).attr('name')]) { + var obj = w2ui[$(this).attr('name')]; + obj[method].apply(obj, Array.prototype.slice.call(arguments, 1)); + return this; + } else { + console.log('ERROR: Method ' + method + ' does not exist on jQuery.w2form'); + } + }; + + // ==================================================== + // -- Implementation of core functionality + + w2form.prototype = { + + get: function (field, returnIndex) { + if (arguments.length === 0) { + var all = []; + for (var f1 in this.fields) { + if (this.fields[f1].name != null) all.push(this.fields[f1].name); + } + return all; + } else { + for (var f2 in this.fields) { + if (this.fields[f2].name == field) { + if (returnIndex === true) return f2; else return this.fields[f2]; + } + } + return null; + } + }, + + set: function (field, obj) { + for (var f in this.fields) { + if (this.fields[f].name == field) { + $.extend(this.fields[f] , obj); + this.refresh(); + return true; + } + } + return false; + }, + + reload: function (callBack) { + var url = (typeof this.url != 'object' ? this.url : this.url.get); + if (url && this.recid != 0) { + // this.clear(); + this.request(callBack); + } else { + // this.refresh(); // no need to refresh + if (typeof callBack == 'function') callBack(); + } + }, + + clear: function () { + this.recid = 0; + this.record = {}; + $().w2tag(); + this.refresh(); + }, + + error: function (msg) { + var obj = this; + // let the management of the error outside of the grid + var eventData = this.trigger({ target: this.name, type: 'error', message: msg , xhr: this.last.xhr }); + if (eventData.isCancelled === true) { + if (typeof callBack == 'function') callBack(); + return; + } + // need a time out because message might be already up) + setTimeout(function () { w2alert(msg, 'Error'); }, 1); + // event after + this.trigger($.extend(eventData, { phase: 'after' })); + }, + + validate: function (showErrors) { + if (typeof showErrors == 'undefined') showErrors = true; + $().w2tag(); // hide all tags before validating + // validate before saving + var errors = []; + for (var f in this.fields) { + var field = this.fields[f]; + if (this.record[field.name] == null) this.record[field.name] = ''; + switch (field.type) { + case 'int': + if (this.record[field.name] && !w2utils.isInt(this.record[field.name])) { + errors.push({ field: field, error: w2utils.lang('Not an integer') }); + } + break; + case 'float': + if (this.record[field.name] && !w2utils.isFloat(this.record[field.name])) { + errors.push({ field: field, error: w2utils.lang('Not a float') }); + } + break; + case 'money': + if (this.record[field.name] && !w2utils.isMoney(this.record[field.name])) { + errors.push({ field: field, error: w2utils.lang('Not in money format') }); + } + break; + case 'color': + case 'hex': + if (this.record[field.name] && !w2utils.isHex(this.record[field.name])) { + errors.push({ field: field, error: w2utils.lang('Not a hex number') }); + } + break; + case 'email': + if (this.record[field.name] && !w2utils.isEmail(this.record[field.name])) { + errors.push({ field: field, error: w2utils.lang('Not a valid email') }); + } + break; + case 'checkbox': + // convert true/false + if (this.record[field.name] == true) this.record[field.name] = 1; else this.record[field.name] = 0; + break; + case 'date': + // format date before submit + if (!field.options.format) field.options.format = w2utils.settings.date_format; + if (this.record[field.name] && !w2utils.isDate(this.record[field.name], field.options.format)) { + errors.push({ field: field, error: w2utils.lang('Not a valid date') + ': ' + field.options.format }); + } else { + } + break; + case 'list': + case 'combo': + break; + case 'enum': + break; + } + // === check required - if field is '0' it should be considered not empty + var val = this.record[field.name]; + if (field.required && (val === '' || ($.isArray(val) && val.length == 0) || ($.isPlainObject(val) && $.isEmptyObject(val)))) { + errors.push({ field: field, error: w2utils.lang('Required field') }); + } + if (field.equalto && this.record[field.name] != this.record[field.equalto]) { + errors.push({ field: field, error: w2utils.lang('Field should be equal to ') + field.equalto }); + } + } + // event before + var eventData = this.trigger({ phase: 'before', target: this.name, type: 'validate', errors: errors }); + if (eventData.isCancelled === true) return; + // show error + if (showErrors) for (var e in eventData.errors) { + var err = eventData.errors[e]; + if (err.field.type == 'radio') { // for radio and checkboxes + $($(err.field.el).parents('div')[0]).w2tag(err.error, { "class": 'w2ui-error' }); + } else if (['enum', 'file'].indexOf(err.field.type) != -1) { + (function (err) { + setTimeout(function () { + var fld = $(err.field.el).data('w2field').helpers.multi; + $(err.field.el).w2tag(err.error); + $(fld).addClass('w2ui-error'); + }, 1); + })(err); + } else { + $(err.field.el).w2tag(err.error, { "class": 'w2ui-error' }); + } + this.goto(errors[0].field.page); + } + // event after + this.trigger($.extend(eventData, { phase: 'after' })); + return errors; + }, + + getChanges: function () { + var differ = function(record, original, result) { + for (var i in record) { + if (typeof record[i] == "object") { + result[i] = differ(record[i], original[i] || {}, {}); + if (!result[i] || $.isEmptyObject(result[i])) delete result[i]; + } else if (record[i] != original[i]) { + result[i] = record[i]; + } + } + return result; + } + return differ(this.record, this.original, {}); + }, + + request: function (postData, callBack) { // if (1) param then it is call back if (2) then postData and callBack + var obj = this; + // check for multiple params + if (typeof postData == 'function') { + callBack = postData; + postData = null; + } + if (typeof postData == 'undefined' || postData == null) postData = {}; + if (!this.url || (typeof this.url == 'object' && !this.url.get)) return; + if (this.recid == null || typeof this.recid == 'undefined') this.recid = 0; + // build parameters list + var params = {}; + // add list params + params['cmd'] = 'get-record'; + params['recid'] = this.recid; + // append other params + $.extend(params, this.postData); + $.extend(params, postData); + // event before + var eventData = this.trigger({ phase: 'before', type: 'request', target: this.name, url: this.url, postData: params }); + if (eventData.isCancelled === true) { if (typeof callBack == 'function') callBack({ status: 'error', message: 'Request aborted.' }); return; } + // default action + this.record = {}; + this.original = {}; + // call server to get data + this.lock(this.msgRefresh); + var url = eventData.url; + if (typeof eventData.url == 'object' && eventData.url.get) url = eventData.url.get; + if (this.last.xhr) try { this.last.xhr.abort(); } catch (e) {}; + // process url with routeData + if (!$.isEmptyObject(obj.routeData)) { + var info = w2utils.parseRoute(url); + if (info.keys.length > 0) { + for (var k = 0; k < info.keys.length; k++) { + if (obj.routeData[info.keys[k].name] == null) continue; + url = url.replace((new RegExp(':'+ info.keys[k].name, 'g')), obj.routeData[info.keys[k].name]); + } + } + } + var ajaxOptions = { + type : 'POST', + url : url, + data : eventData.postData, + dataType : 'text' // expected from server + }; + if (w2utils.settings.dataType == 'HTTP') { + ajaxOptions.data = String($.param(ajaxOptions.data, false)).replace(/%5B/g, '[').replace(/%5D/g, ']'); + } + if (w2utils.settings.dataType == 'RESTFULL') { + ajaxOptions.type = 'GET'; + ajaxOptions.data = String($.param(ajaxOptions.data, false)).replace(/%5B/g, '[').replace(/%5D/g, ']'); + } + if (w2utils.settings.dataType == 'JSON') { + ajaxOptions.type = 'POST'; + ajaxOptions.data = JSON.stringify(ajaxOptions.data); + ajaxOptions.contentType = 'application/json'; + } + this.last.xhr = $.ajax(ajaxOptions) + .done(function (data, status, xhr) { + obj.unlock(); + // event before + var eventData = obj.trigger({ phase: 'before', target: obj.name, type: 'load', xhr: xhr }); + if (eventData.isCancelled === true) { + if (typeof callBack == 'function') callBack({ status: 'error', message: 'Request aborted.' }); + return; + } + // parse server response + var data; + var responseText = obj.last.xhr.responseText; + if (status != 'error') { + // default action + if (typeof responseText != 'undefined' && responseText != '') { + // check if the onLoad handler has not already parsed the data + if (typeof responseText == "object") { + data = responseText; + } else { + // $.parseJSON or $.getJSON did not work because those expect perfect JSON data - where everything is in double quotes + // + // TODO: avoid (potentially malicious) code injection from the response. + try { eval('data = '+ responseText); } catch (e) { } + } + if (typeof data == 'undefined') { + data = { + status : 'error', + message : obj.msgNotJSON, + responseText : responseText + } + } + if (data['status'] == 'error') { + obj.error(data['message']); + } else { + obj.record = $.extend({}, data.record); + obj.original = $.extend({}, data.record); + } + } + } else { + obj.error('AJAX Error ' + xhr.status + ': '+ xhr.statusText); + data = { + status : 'error', + message : obj.msgAJAXerror, + responseText : responseText + }; + } + // event after + obj.trigger($.extend(eventData, { phase: 'after' })); + obj.refresh(); + // call back + if (typeof callBack == 'function') callBack(data); + }) + .fail(function (xhr, status, error) { + // trigger event + var errorObj = { status: status, error: error, rawResponseText: xhr.responseText }; + var eventData2 = obj.trigger({ phase: 'before', type: 'error', error: errorObj, xhr: xhr }); + if (eventData2.isCancelled === true) return; + // default behavior + if (status != 'abort') { + var data; + try { data = $.parseJSON(xhr.responseText) } catch (e) {} + console.log('ERROR: Server communication failed.', + '\n EXPECTED:', { status: 'success', items: [{ id: 1, text: 'item' }] }, + '\n OR:', { status: 'error', message: 'error message' }, + '\n RECEIVED:', typeof data == 'object' ? data : xhr.responseText); + } + // event after + obj.trigger($.extend(eventData2, { phase: 'after' })); + }); + // event after + this.trigger($.extend(eventData, { phase: 'after' })); + }, + + submit: function (postData, callBack) { + return this.save(postData, callBack); + }, + + save: function (postData, callBack) { + var obj = this; + $(this.box).find(':focus').change(); // trigger onchange + // check for multiple params + if (typeof postData == 'function') { + callBack = postData; + postData = null; + } + // validation + var errors = obj.validate(true); + if (errors.length !== 0) return; + // submit save + if (typeof postData == 'undefined' || postData == null) postData = {}; + if (!obj.url || (typeof obj.url == 'object' && !obj.url.save)) { + console.log("ERROR: Form cannot be saved because no url is defined."); + return; + } + obj.lock(obj.msgSaving + ' <span id="'+ obj.name +'_progress"></span>'); + // need timer to allow to lock + setTimeout(function () { + // build parameters list + var params = {}; + // add list params + params['cmd'] = 'save-record'; + params['recid'] = obj.recid; + // append other params + $.extend(params, obj.postData); + $.extend(params, postData); + params.record = $.extend(true, {}, obj.record); + // event before + var eventData = obj.trigger({ phase: 'before', type: 'submit', target: obj.name, url: obj.url, postData: params }); + if (eventData.isCancelled === true) return; + // default action + var url = eventData.url; + if (typeof eventData.url == 'object' && eventData.url.save) url = eventData.url.save; + if (obj.last.xhr) try { obj.last.xhr.abort(); } catch (e) {}; + // process url with routeData + if (!$.isEmptyObject(obj.routeData)) { + var info = w2utils.parseRoute(url); + if (info.keys.length > 0) { + for (var k = 0; k < info.keys.length; k++) { + if (obj.routeData[info.keys[k].name] == null) continue; + url = url.replace((new RegExp(':'+ info.keys[k].name, 'g')), obj.routeData[info.keys[k].name]); + } + } + } + var ajaxOptions = { + type : 'POST', + url : url, + data : eventData.postData, + dataType : 'text', // expected from server + xhr : function() { + var xhr = new window.XMLHttpRequest(); + // upload + xhr.upload.addEventListener("progress", function(evt) { + if (evt.lengthComputable) { + var percent = Math.round(evt.loaded / evt.total * 100); + $('#'+ obj.name + '_progress').text(''+ percent + '%'); + } + }, false); + return xhr; + } + }; + if (w2utils.settings.dataType == 'HTTP') { + ajaxOptions.data = String($.param(ajaxOptions.data, false)).replace(/%5B/g, '[').replace(/%5D/g, ']'); + } + if (w2utils.settings.dataType == 'RESTFULL') { + if (obj.recid != 0) ajaxOptions.type = 'PUT'; + ajaxOptions.data = String($.param(ajaxOptions.data, false)).replace(/%5B/g, '[').replace(/%5D/g, ']'); + } + if (w2utils.settings.dataType == 'JSON') { + ajaxOptions.type = 'POST'; + ajaxOptions.data = JSON.stringify(ajaxOptions.data); + ajaxOptions.contentType = 'application/json'; + } + + obj.last.xhr = $.ajax(ajaxOptions) + .done(function (data, status, xhr) { + obj.unlock(); + // event before + var eventData = obj.trigger({ phase: 'before', target: obj.name, type: 'save', xhr: xhr, status: status }); + if (eventData.isCancelled === true) return; + // parse server response + var data; + var responseText = xhr.responseText; + if (status != 'error') { + // default action + if (typeof responseText != 'undefined' && responseText != '') { + // check if the onLoad handler has not already parsed the data + if (typeof responseText == "object") { + data = responseText; + } else { + // $.parseJSON or $.getJSON did not work because those expect perfect JSON data - where everything is in double quotes + // + // TODO: avoid (potentially malicious) code injection from the response. + try { eval('data = '+ responseText); } catch (e) { } + } + if (typeof data == 'undefined') { + data = { + status : 'error', + message : obj.msgNotJSON, + responseText : responseText + } + } + if (data['status'] == 'error') { + obj.error(data['message']); + } else { + obj.original = $.extend({}, obj.record); + } + } + } else { + obj.error('AJAX Error ' + xhr.status + ': '+ xhr.statusText); + data = { + status : 'error', + message : obj.msgAJAXerror, + responseText : responseText + }; + } + // event after + obj.trigger($.extend(eventData, { phase: 'after' })); + obj.refresh(); + // call back + if (data.status == 'success' && typeof callBack == 'function') callBack(data); + }) + .fail(function (xhr, status, error) { + // trigger event + var errorObj = { status: status, error: error, rawResponseText: xhr.responseText }; + var eventData2 = obj.trigger({ phase: 'before', type: 'error', error: errorObj, xhr: xhr }); + if (eventData2.isCancelled === true) return; + // default behavior + console.log('ERROR: server communication failed. The server should return', + { status: 'success' }, 'OR', { status: 'error', message: 'error message' }, + ', instead the AJAX request produced this: ', errorObj); + // event after + obj.trigger($.extend(eventData2, { phase: 'after' })); + }); + // event after + obj.trigger($.extend(eventData, { phase: 'after' })); + }, 50); + }, + + lock: function (msg, showSpinner) { + var box = $(this.box).find('> div:first-child'); + var args = Array.prototype.slice.call(arguments, 0); + args.unshift(box); + w2utils.lock.apply(window, args); + }, + + unlock: function () { + var obj = this; + setTimeout(function () { w2utils.unlock(obj.box); }, 25); // needed timer so if server fast, it will not flash + }, + + goto: function (page) { + if (typeof page != 'undefined') this.page = page; + // if it was auto size, resize it + if ($(this.box).data('auto-size') === true) $(this.box).height(0); + this.refresh(); + }, + + generateHTML: function () { + var pages = []; // array for each page + var group = ''; + var page; + for (var f in this.fields) { + var html = ''; + var field = this.fields[f]; + if (typeof field.html == 'undefined') field.html = {}; + field.html = $.extend(true, { caption: '', span: 6, attr: '', text: '', page: 0 }, field.html); + if (typeof page == 'undefined') page = field.html.page; + if (field.html.caption == '') field.html.caption = field.name; + var input = '<input name="'+ field.name +'" type="text" '+ field.html.attr +'/>'; + if ((field.type === 'pass') || (field.type === 'password')){ + input = '<input name="' + field.name + '" type = "password" ' + field.html.attr + '/>'; + } + if (field.type == 'checkbox') input = '<input name="'+ field.name +'" type="checkbox" '+ field.html.attr +'/>'; + if (field.type == 'textarea') input = '<textarea name="'+ field.name +'" '+ field.html.attr +'></textarea>'; + if (field.type == 'toggle') input = '<input name="'+ field.name +'" type="checkbox" '+ field.html.attr +' class="w2ui-toggle"/><div><div></div></div>'; + if (field.html.group) { + if (group != '') html += '\n </div>'; + html += '\n <div class="w2ui-group-title">'+ field.html.group + '</div>\n <div class="w2ui-group">'; + group = field.html.group; + } + if (field.html.page != page && group != '') { + pages[pages.length-1] += '\n </div>'; + group = ''; + } + html += '\n <div class="w2ui-field '+ (typeof field.html.span != 'undefined' ? 'w2ui-span'+ field.html.span : '') +'">'+ + '\n <label>' + w2utils.lang(field.html.caption) +'</label>'+ + '\n <div>'+ input + w2utils.lang(field.html.text) + '</div>'+ + '\n </div>'; + if (typeof pages[field.html.page] == 'undefined') pages[field.html.page] = ''; + pages[field.html.page] += html; + page = field.html.page; + } + if (group != '') pages[pages.length-1] += '\n </div>'; + if (this.tabs.tabs) { + for (var i = 0; i < this.tabs.tabs.length; i++) if (typeof pages[i] == 'undefined') pages[i] = ''; + } + for (var p in pages) pages[p] = '<div class="w2ui-page page-'+ p +'">' + pages[p] + '\n</div>'; + // buttons if any + var buttons = ''; + if (!$.isEmptyObject(this.actions)) { + var addClass = ''; + buttons += '\n<div class="w2ui-buttons">'; + for (var a in this.actions) { + if (['save', 'update', 'create'].indexOf(a.toLowerCase()) != -1) addClass = 'btn-green'; else addClass = ''; + buttons += '\n <button name="'+ a +'" class="btn '+ addClass +'">'+ w2utils.lang(a) +'</button>'; + } + buttons += '\n</div>'; + } + return pages.join('') + buttons; + }, + + action: function (action, event) { + // event before + var eventData = this.trigger({ phase: 'before', target: action, type: 'action', originalEvent: event }); + if (eventData.isCancelled === true) return; + // default actions + if (typeof (this.actions[action]) == 'function') { + this.actions[action].call(this, event); + } + // event after + this.trigger($.extend(eventData, { phase: 'after' })); + }, + + resize: function () { + var obj = this; + // event before + var eventData = this.trigger({ phase: 'before', target: this.name, type: 'resize' }); + if (eventData.isCancelled === true) return; + // default behaviour + var main = $(this.box).find('> div'); + var header = $(this.box).find('> div .w2ui-form-header'); + var toolbar = $(this.box).find('> div .w2ui-form-toolbar'); + var tabs = $(this.box).find('> div .w2ui-form-tabs'); + var page = $(this.box).find('> div .w2ui-page'); + var cpage = $(this.box).find('> div .w2ui-page.page-'+ this.page); + var dpage = $(this.box).find('> div .w2ui-page.page-'+ this.page + ' > div'); + var buttons = $(this.box).find('> div .w2ui-buttons'); + // if no height, calculate it + resizeElements(); + if (parseInt($(this.box).height()) == 0 || $(this.box).data('auto-size') === true) { + $(this.box).height( + (header.length > 0 ? w2utils.getSize(header, 'height') : 0) + + ((typeof this.tabs === 'object' && $.isArray(this.tabs.tabs) && this.tabs.tabs.length > 0) ? w2utils.getSize(tabs, 'height') : 0) + + ((typeof this.toolbar == 'object' && $.isArray(this.toolbar.items) && this.toolbar.items.length > 0) ? w2utils.getSize(toolbar, 'height') : 0) + + (page.length > 0 ? w2utils.getSize(dpage, 'height') + w2utils.getSize(cpage, '+height') + 12 : 0) + // why 12 ??? + (buttons.length > 0 ? w2utils.getSize(buttons, 'height') : 0) + ); + $(this.box).data('auto-size', true); + } + resizeElements(); + // event after + obj.trigger($.extend(eventData, { phase: 'after' })); + + function resizeElements() { + // resize elements + main.width($(obj.box).width()).height($(obj.box).height()); + toolbar.css('top', (obj.header != '' ? w2utils.getSize(header, 'height') : 0)); + tabs.css('top', (obj.header != '' ? w2utils.getSize(header, 'height') : 0) + + ((typeof obj.toolbar == 'object' && $.isArray(obj.toolbar.items) && obj.toolbar.items.length > 0) ? w2utils.getSize(toolbar, 'height') : 0)); + page.css('top', (obj.header != '' ? w2utils.getSize(header, 'height') : 0) + + ((typeof obj.toolbar == 'object' && $.isArray(obj.toolbar.items) && obj.toolbar.items.length > 0) ? w2utils.getSize(toolbar, 'height') + 5 : 0) + + ((typeof obj.tabs === 'object' && $.isArray(obj.tabs.tabs) && obj.tabs.tabs.length > 0) ? w2utils.getSize(tabs, 'height') + 5 : 0)); + page.css('bottom', (buttons.length > 0 ? w2utils.getSize(buttons, 'height') : 0)); + } + }, + + refresh: function () { + var time = (new Date()).getTime(); + var obj = this; + if (!this.box) return; + if (!this.isGenerated || typeof $(this.box).html() == 'undefined') return; + // update what page field belongs + $(this.box).find('input, textarea, select').each(function (index, el) { + var name = (typeof $(el).attr('name') != 'undefined' ? $(el).attr('name') : $(el).attr('id')); + var field = obj.get(name); + if (field) { + // find page + var div = $(el).parents('.w2ui-page'); + if (div.length > 0) { + for (var i = 0; i < 100; i++) { + if (div.hasClass('page-'+i)) { field.page = i; break; } + } + } + } + }); + // event before + var eventData = this.trigger({ phase: 'before', target: this.name, type: 'refresh', page: this.page }) + if (eventData.isCancelled === true) return; + // default action + $(this.box).find('.w2ui-page').hide(); + $(this.box).find('.w2ui-page.page-' + this.page).show(); + $(this.box).find('.w2ui-form-header').html(this.header); + // refresh tabs if needed + if (typeof this.tabs === 'object' && $.isArray(this.tabs.tabs) && this.tabs.tabs.length > 0) { + $('#form_'+ this.name +'_tabs').show(); + this.tabs.active = this.tabs.tabs[this.page].id; + this.tabs.refresh(); + } else { + $('#form_'+ this.name +'_tabs').hide(); + } + // refresh tabs if needed + if (typeof this.toolbar == 'object' && $.isArray(this.toolbar.items) && this.toolbar.items.length > 0) { + $('#form_'+ this.name +'_toolbar').show(); + this.toolbar.refresh(); + } else { + $('#form_'+ this.name +'_toolbar').hide(); + } + // refresh values of all fields + for (var f in this.fields) { + var field = this.fields[f]; + if (typeof field.name == 'undefined' && typeof field.field != 'undefined') field.name = field.field; + if (typeof field.field == 'undefined' && typeof field.name != 'undefined') field.field = field.name; + field.$el = $(this.box).find('[name="'+ String(field.name).replace(/\\/g, '\\\\') +'"]'); + field.el = field.$el[0]; + if (typeof field.el == 'undefined') { + console.log('ERROR: Cannot associate field "'+ field.name + '" with html control. Make sure html control exists with the same name.'); + //return; + } + if (field.el) field.el.id = field.name; + var tmp = $(field).data('w2field'); + if (tmp) tmp.clear(); + $(field.$el).off('change').on('change', function () { + var value_new = this.value; + var value_previous = obj.record[this.name] ? obj.record[this.name] : ''; + var field = obj.get(this.name); + if (['list', 'enum', 'file'].indexOf(field.type) != -1 && $(this).data('selected')) { + var nv = $(this).data('selected'); + var cv = obj.record[this.name]; + if ($.isArray(nv)) { + value_new = []; + for (var i in nv) value_new[i] = $.extend(true, {}, nv[i]); // clone array + } + if ($.isPlainObject(nv)) { + value_new = $.extend(true, {}, nv); // clone object + } + if ($.isArray(cv)) { + value_previous = []; + for (var i in cv) value_previous[i] = $.extend(true, {}, cv[i]); // clone array + } + if ($.isPlainObject(cv)) { + value_previous = $.extend(true, {}, cv); // clone object + } + } + if (field.type == 'toggle') value_new = ($(this).prop('checked') ? 1 : 0); + // clean extra chars + if (['int', 'float', 'percent', 'money', 'currency'].indexOf(field.type) != -1) { + value_new = $(this).data('w2field').clean(value_new); + } + if (value_new === value_previous) return; + // event before + var eventData = obj.trigger({ phase: 'before', target: this.name, type: 'change', value_new: value_new, value_previous: value_previous }); + if (eventData.isCancelled === true) { + $(this).val(obj.record[this.name]); // return previous value + return; + } + // default action + var val = this.value; + if (this.type == 'select') val = this.value; + if (this.type == 'checkbox') val = this.checked ? true : false; + if (this.type == 'radio') { + field.$el.each(function (index, el) { + if (el.checked) val = el.value; + }); + } + if (['int', 'float', 'percent', 'money', 'currency', 'list', 'combo', 'enum', 'file', 'toggle'].indexOf(field.type) != -1) { + val = value_new; + } + if (['enum', 'file'].indexOf(field.type) != -1) { + if (val.length > 0) { + var fld = $(field.el).data('w2field').helpers.multi; + $(fld).removeClass('w2ui-error'); + } + } + obj.record[this.name] = val; + // event after + obj.trigger($.extend(eventData, { phase: 'after' })); + }); + if (field.required) { + $(field.el).parent().parent().addClass('w2ui-required'); + } else { + $(field.el).parent().parent().removeClass('w2ui-required'); + } + } + // attach actions on buttons + $(this.box).find('button, input[type=button]').each(function (index, el) { + $(el).off('click').on('click', function (event) { + var action = this.value; + if (this.id) action = this.id; + if (this.name) action = this.name; + obj.action(action, event); + }); + }); + // init controls with record + for (var f in this.fields) { + var field = this.fields[f]; + var value = (typeof this.record[field.name] != 'undefined' ? this.record[field.name] : ''); + if (!field.el) continue; + field.type = String(field.type).toLowerCase(); + if (!field.options) field.options = {}; + switch (field.type) { + case 'text': + case 'textarea': + case 'email': + case 'pass': + case 'password': + field.el.value = value; + break; + case 'int': + case 'float': + case 'money': + case 'currency': + case 'percent': + case 'hex': + case 'alphanumeric': + case 'color': + case 'date': + case 'time': + field.el.value = value; + $(field.el).w2field($.extend({}, field.options, { type: field.type })); + break; + case 'toggle': + if (w2utils.isFloat(value)) value = parseFloat(value); + $(field.el).prop('checked', (value ? true : false)); + this.record[field.name] = (value ? 1 : 0); + break; + // enums + case 'list': + case 'combo': + if (field.type == 'list' && !$.isPlainObject(value)) { + // find value from items + for (var i in field.options.items) { + var item = field.options.items[i]; + if ($.isPlainObject(item) && item.id == value) { + value = $.extend(true, {}, item); + obj.record[field.name] = value; + break; + } else if (i == value) { + value = { id: i, text: item }; + obj.record[field.name] = value; + break; + } + } + } else if (field.type == 'combo' && !$.isPlainObject(value)) { + field.el.value = value; + } else if ($.isPlainObject(value) && typeof value.text != 'undefined') { + field.el.value = value.text; + } else { + field.el.value = ''; + } + if (!$.isPlainObject(value)) value = {}; + $(field.el).w2field($.extend({}, field.options, { type: field.type, selected: value })); + break; + case 'enum': + case 'file': + if (!$.isArray(value)) value = []; + $(field.el).w2field($.extend({}, field.options, { type: field.type, selected: value })); + break; + + // standard HTML + case 'select': + // generate options + var items = field.options.items; + if (typeof items != 'undefined' && items.length > 0) { + items = w2obj.field.prototype.normMenu(items); + $(field.el).html(''); + for (var it in items) { + $(field.el).append('<option value="'+ items[it].id +'">' + items[it].text + '</option'); + } + } + $(field.el).val(value); + break; + case 'radio': + $(field.$el).prop('checked', false).each(function (index, el) { + if ($(el).val() == value) $(el).prop('checked', true); + }); + break; + case 'checkbox': + $(field.el).prop('checked', value ? true : false); + break; + default: + $(field.el).w2field($.extend({}, field.options, { type: field.type })); + break; + } + } + // wrap pages in div + var tmp = $(this.box).find('.w2ui-page'); + for (var i = 0; i < tmp.length; i++) { + if ($(tmp[i]).find('> *').length > 1) $(tmp[i]).wrapInner('<div></div>'); + } + // event after + this.trigger($.extend(eventData, { phase: 'after' })); + this.resize(); + return (new Date()).getTime() - time; + }, + + render: function (box) { + var time = (new Date()).getTime(); + var obj = this; + if (typeof box == 'object') { + // remove from previous box + if ($(this.box).find('#form_'+ this.name +'_tabs').length > 0) { + $(this.box).removeAttr('name') + .removeClass('w2ui-reset w2ui-form') + .html(''); + } + this.box = box; + } + if (!this.isGenerated) return; + if (!this.box) return; + // event before + var eventData = this.trigger({ phase: 'before', target: this.name, type: 'render', box: (typeof box != 'undefined' ? box : this.box) }); + if (eventData.isCancelled === true) return; + // default actions + if ($.isEmptyObject(this.original) && !$.isEmptyObject(this.record)) { + this.original = $.extend(true, {}, this.record); + } + var html = '<div>' + + (this.header != '' ? '<div class="w2ui-form-header">' + this.header + '</div>' : '') + + ' <div id="form_'+ this.name +'_toolbar" class="w2ui-form-toolbar"></div>' + + ' <div id="form_'+ this.name +'_tabs" class="w2ui-form-tabs"></div>' + + this.formHTML + + '</div>'; + $(this.box).attr('name', this.name) + .addClass('w2ui-reset w2ui-form') + .html(html); + if ($(this.box).length > 0) $(this.box)[0].style.cssText += this.style; + + // init toolbar regardless it is defined or not + if (typeof this.toolbar.render !== 'function') { + this.toolbar = $().w2toolbar($.extend({}, this.toolbar, { name: this.name +'_toolbar', owner: this })); + this.toolbar.on('click', function (event) { + var eventData = obj.trigger({ phase: 'before', type: 'toolbar', target: event.target, originalEvent: event }); + if (eventData.isCancelled === true) return; + // no default action + obj.trigger($.extend(eventData, { phase: 'after' })); + }); + } + if (typeof this.toolbar == 'object' && typeof this.toolbar.render == 'function') { + this.toolbar.render($('#form_'+ this.name +'_toolbar')[0]); + } + // init tabs regardless it is defined or not + if (typeof this.tabs.render !== 'function') { + this.tabs = $().w2tabs($.extend({}, this.tabs, { name: this.name +'_tabs', owner: this })); + this.tabs.on('click', function (event) { + obj.goto(this.get(event.target, true)); + }); + } + if (typeof this.tabs == 'object' && typeof this.tabs.render == 'function') { + this.tabs.render($('#form_'+ this.name +'_tabs')[0]); + } + // event after + this.trigger($.extend(eventData, { phase: 'after' })); + // after render actions + this.resize(); + var url = (typeof this.url != 'object' ? this.url : this.url.get); + if (url && this.recid != 0) { + this.request(); + } else { + this.refresh(); + } + // attach to resize event + if ($('.w2ui-layout').length == 0) { // if there is layout, it will send a resize event + this.tmp_resize = function (event) { w2ui[obj.name].resize(); } + $(window).off('resize', 'body').on('resize', 'body', this.tmp_resize); + } + setTimeout(function () { obj.resize(); obj.refresh(); }, 150); // need timer because resize is on timer + // focus on load + function focusEl() { + var inputs = $(obj.box).find('input, select, textarea'); + if (inputs.length > obj.focus) inputs[obj.focus].focus(); + } + if (this.focus >= 0) setTimeout(focusEl, 500); // need timeout to allow form to render + return (new Date()).getTime() - time; + }, + + destroy: function () { + // event before + var eventData = this.trigger({ phase: 'before', target: this.name, type: 'destroy' }); + if (eventData.isCancelled === true) return; + // clean up + if (typeof this.toolbar == 'object' && this.toolbar.destroy) this.toolbar.destroy(); + if (typeof this.tabs == 'object' && this.tabs.destroy) this.tabs.destroy(); + if ($(this.box).find('#form_'+ this.name +'_tabs').length > 0) { + $(this.box) + .removeAttr('name') + .removeClass('w2ui-reset w2ui-form') + .html(''); + } + delete w2ui[this.name]; + // event after + this.trigger($.extend(eventData, { phase: 'after' })); + $(window).off('resize', 'body') + } + }; + + $.extend(w2form.prototype, w2utils.event); + w2obj.form = w2form; +})(); |