application.js 77 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224
  1. /*
  2. * jQuery Templates Plugin 1.0.0pre
  3. * http://github.com/jquery/jquery-tmpl
  4. * Requires jQuery 1.4.2
  5. *
  6. * Copyright Software Freedom Conservancy, Inc.
  7. * Dual licensed under the MIT or GPL Version 2 licenses.
  8. * http://jquery.org/license
  9. */
  10. (function(a){var r=a.fn.domManip,d="_tmplitem",q=/^[^<]*(<[\w\W]+>)[^>]*$|\{\{\! /,b={},f={},e,p={key:0,data:{}},i=0,c=0,l=[];function g(g,d,h,e){var c={data:e||(e===0||e===false)?e:d?d.data:{},_wrap:d?d._wrap:null,tmpl:null,parent:d||null,nodes:[],calls:u,nest:w,wrap:x,html:v,update:t};g&&a.extend(c,g,{nodes:[],parent:d});if(h){c.tmpl=h;c._ctnt=c._ctnt||c.tmpl(a,c);c.key=++i;(l.length?f:b)[i]=c}return c}a.each({appendTo:"append",prependTo:"prepend",insertBefore:"before",insertAfter:"after",replaceAll:"replaceWith"},function(f,d){a.fn[f]=function(n){var g=[],i=a(n),k,h,m,l,j=this.length===1&&this[0].parentNode;e=b||{};if(j&&j.nodeType===11&&j.childNodes.length===1&&i.length===1){i[d](this[0]);g=this}else{for(h=0,m=i.length;h<m;h++){c=h;k=(h>0?this.clone(true):this).get();a(i[h])[d](k);g=g.concat(k)}c=0;g=this.pushStack(g,f,i.selector)}l=e;e=null;a.tmpl.complete(l);return g}});a.fn.extend({tmpl:function(d,c,b){return a.tmpl(this[0],d,c,b)},tmplItem:function(){return a.tmplItem(this[0])},template:function(b){return a.template(b,this[0])},domManip:function(d,m,k){if(d[0]&&a.isArray(d[0])){var g=a.makeArray(arguments),h=d[0],j=h.length,i=0,f;while(i<j&&!(f=a.data(h[i++],"tmplItem")));if(f&&c)g[2]=function(b){a.tmpl.afterManip(this,b,k)};r.apply(this,g)}else r.apply(this,arguments);c=0;!e&&a.tmpl.complete(b);return this}});a.extend({tmpl:function(d,h,e,c){var i,k=!c;if(k){c=p;d=a.template[d]||a.template(null,d);f={}}else if(!d){d=c.tmpl;b[c.key]=c;c.nodes=[];c.wrapped&&n(c,c.wrapped);return a(j(c,null,c.tmpl(a,c)))}if(!d)return[];if(typeof h==="function")h=h.call(c||{});e&&e.wrapped&&n(e,e.wrapped);i=a.isArray(h)?a.map(h,function(a){return a?g(e,c,d,a):null}):[g(e,c,d,h)];return k?a(j(c,null,i)):i},tmplItem:function(b){var c;if(b instanceof a)b=b[0];while(b&&b.nodeType===1&&!(c=a.data(b,"tmplItem"))&&(b=b.parentNode));return c||p},template:function(c,b){if(b){if(typeof b==="string")b=o(b);else if(b instanceof a)b=b[0]||{};if(b.nodeType)b=a.data(b,"tmpl")||a.data(b,"tmpl",o(b.innerHTML));return typeof c==="string"?(a.template[c]=b):b}return c?typeof c!=="string"?a.template(null,c):a.template[c]||a.template(null,q.test(c)?c:a(c)):null},encode:function(a){return(""+a).split("<").join("&lt;").split(">").join("&gt;").split('"').join("&#34;").split("'").join("&#39;")}});a.extend(a.tmpl,{tag:{tmpl:{_default:{$2:"null"},open:"if($notnull_1){__=__.concat($item.nest($1,$2));}"},wrap:{_default:{$2:"null"},open:"$item.calls(__,$1,$2);__=[];",close:"call=$item.calls();__=call._.concat($item.wrap(call,__));"},each:{_default:{$2:"$index, $value"},open:"if($notnull_1){$.each($1a,function($2){with(this){",close:"}});}"},"if":{open:"if(($notnull_1) && $1a){",close:"}"},"else":{_default:{$1:"true"},open:"}else if(($notnull_1) && $1a){"},html:{open:"if($notnull_1){__.push($1a);}"},"=":{_default:{$1:"$data"},open:"if($notnull_1){__.push($.encode($1a));}"},"!":{open:""}},complete:function(){b={}},afterManip:function(f,b,d){var e=b.nodeType===11?a.makeArray(b.childNodes):b.nodeType===1?[b]:[];d.call(f,b);m(e);c++}});function j(e,g,f){var b,c=f?a.map(f,function(a){return typeof a==="string"?e.key?a.replace(/(<\w+)(?=[\s>])(?![^>]*_tmplitem)([^>]*)/g,"$1 "+d+'="'+e.key+'" $2'):a:j(a,e,a._ctnt)}):e;if(g)return c;c=c.join("");c.replace(/^\s*([^<\s][^<]*)?(<[\w\W]+>)([^>]*[^>\s])?\s*$/,function(f,c,e,d){b=a(e).get();m(b);if(c)b=k(c).concat(b);if(d)b=b.concat(k(d))});return b?b:k(c)}function k(c){var b=document.createElement("div");b.innerHTML=c;return a.makeArray(b.childNodes)}function o(b){return new Function("jQuery","$item","var $=jQuery,call,__=[],$data=$item.data;with($data){__.push('"+a.trim(b).replace(/([\\'])/g,"\\$1").replace(/[\r\t\n]/g," ").replace(/\$\{([^\}]*)\}/g,"{{= $1}}").replace(/\{\{(\/?)(\w+|.)(?:\(((?:[^\}]|\}(?!\}))*?)?\))?(?:\s+(.*?)?)?(\(((?:[^\}]|\}(?!\}))*?)\))?\s*\}\}/g,function(m,l,k,g,b,c,d){var j=a.tmpl.tag[k],i,e,f;if(!j)throw"Unknown template tag: "+k;i=j._default||[];if(c&&!/\w$/.test(b)){b+=c;c=""}if(b){b=h(b);d=d?","+h(d)+")":c?")":"";e=c?b.indexOf(".")>-1?b+h(c):"("+b+").call($item"+d:b;f=c?e:"(typeof("+b+")==='function'?("+b+").call($item):("+b+"))"}else f=e=i.$1||"null";g=h(g);return"');"+j[l?"close":"open"].split("$notnull_1").join(b?"typeof("+b+")!=='undefined' && ("+b+")!=null":"true").split("$1a").join(f).split("$1").join(e).split("$2").join(g||i.$2||"")+"__.push('"})+"');}return __;")}function n(c,b){c._wrap=j(c,true,a.isArray(b)?b:[q.test(b)?b:a(b).html()]).join("")}function h(a){return a?a.replace(/\\'/g,"'").replace(/\\\\/g,"\\"):null}function s(b){var a=document.createElement("div");a.appendChild(b.cloneNode(true));return a.innerHTML}function m(o){var n="_"+c,k,j,l={},e,p,h;for(e=0,p=o.length;e<p;e++){if((k=o[e]).nodeType!==1)continue;j=k.getElementsByTagName("*");for(h=j.length-1;h>=0;h--)m(j[h]);m(k)}function m(j){var p,h=j,k,e,m;if(m=j.getAttribute(d)){while(h.parentNode&&(h=h.parentNode).nodeType===1&&!(p=h.getAttribute(d)));if(p!==m){h=h.parentNode?h.nodeType===11?0:h.getAttribute(d)||0:0;if(!(e=b[m])){e=f[m];e=g(e,b[h]||f[h]);e.key=++i;b[i]=e}c&&o(m)}j.removeAttribute(d)}else if(c&&(e=a.data(j,"tmplItem"))){o(e.key);b[e.key]=e;h=a.data(j.parentNode,"tmplItem");h=h?h.key:0}if(e){k=e;while(k&&k.key!=h){k.nodes.push(j);k=k.parent}delete e._ctnt;delete e._wrap;a.data(j,"tmplItem",e)}function o(a){a=a+n;e=l[a]=l[a]||g(e,b[e.parent.key+n]||e.parent)}}}function u(a,d,c,b){if(!a)return l.pop();l.push({_:a,tmpl:d,item:this,data:c,options:b})}function w(d,c,b){return a.tmpl(a.template(d),c,b,this)}function x(b,d){var c=b.options||{};c.wrapped=d;return a.tmpl(a.template(b.tmpl),b.data,c,b.item)}function v(d,c){var b=this._wrap;return a.map(a(a.isArray(b)?b.join(""):b).filter(d||"*"),function(a){return c?a.innerText||a.textContent:a.outerHTML||s(a)})}function t(){var b=this.nodes;a.tmpl(null,null,null,this).insertBefore(b[0]);a(b).remove()}})(jQuery);
  11. /**
  12. * http://github.com/valums/file-uploader
  13. *
  14. * Multiple file upload component with progress-bar, drag-and-drop.
  15. * © 2010 Andrew Valums ( andrew(at)valums.com )
  16. *
  17. * Licensed under GNU GPL 2 or later, see license.txt.
  18. */
  19. //
  20. // Helper functions
  21. //
  22. var qq = qq || {};
  23. /**
  24. * Adds all missing properties from second obj to first obj
  25. */
  26. qq.extend = function(first, second){
  27. for (var prop in second){
  28. first[prop] = second[prop];
  29. }
  30. };
  31. /**
  32. * Searches for a given element in the array, returns -1 if it is not present.
  33. * @param {Number} [from] The index at which to begin the search
  34. */
  35. qq.indexOf = function(arr, elt, from){
  36. if (arr.indexOf) return arr.indexOf(elt, from);
  37. from = from || 0;
  38. var len = arr.length;
  39. if (from < 0) from += len;
  40. for (; from < len; from++){
  41. if (from in arr && arr[from] === elt){
  42. return from;
  43. }
  44. }
  45. return -1;
  46. };
  47. qq.getUniqueId = (function(){
  48. var id = 0;
  49. return function(){ return id++; };
  50. })();
  51. //
  52. // Events
  53. qq.attach = function(element, type, fn){
  54. if (element.addEventListener){
  55. element.addEventListener(type, fn, false);
  56. } else if (element.attachEvent){
  57. element.attachEvent('on' + type, fn);
  58. }
  59. };
  60. qq.detach = function(element, type, fn){
  61. if (element.removeEventListener){
  62. element.removeEventListener(type, fn, false);
  63. } else if (element.attachEvent){
  64. element.detachEvent('on' + type, fn);
  65. }
  66. };
  67. qq.preventDefault = function(e){
  68. if (e.preventDefault){
  69. e.preventDefault();
  70. } else{
  71. e.returnValue = false;
  72. }
  73. };
  74. //
  75. // Node manipulations
  76. /**
  77. * Insert node a before node b.
  78. */
  79. qq.insertBefore = function(a, b){
  80. b.parentNode.insertBefore(a, b);
  81. };
  82. qq.remove = function(element){
  83. element.parentNode.removeChild(element);
  84. };
  85. qq.contains = function(parent, descendant){
  86. // compareposition returns false in this case
  87. if (parent == descendant) return true;
  88. if (parent.contains){
  89. return parent.contains(descendant);
  90. } else {
  91. return !!(descendant.compareDocumentPosition(parent) & 8);
  92. }
  93. };
  94. /**
  95. * Creates and returns element from html string
  96. * Uses innerHTML to create an element
  97. */
  98. qq.toElement = (function(){
  99. var div = document.createElement('div');
  100. return function(html){
  101. div.innerHTML = html;
  102. var element = div.firstChild;
  103. div.removeChild(element);
  104. return element;
  105. };
  106. })();
  107. //
  108. // Node properties and attributes
  109. /**
  110. * Sets styles for an element.
  111. * Fixes opacity in IE6-8.
  112. */
  113. qq.css = function(element, styles){
  114. if (styles.opacity != null){
  115. if (typeof element.style.opacity != 'string' && typeof(element.filters) != 'undefined'){
  116. styles.filter = 'alpha(opacity=' + Math.round(100 * styles.opacity) + ')';
  117. }
  118. }
  119. qq.extend(element.style, styles);
  120. };
  121. qq.hasClass = function(element, name){
  122. var re = new RegExp('(^| )' + name + '( |$)');
  123. return re.test(element.className);
  124. };
  125. qq.addClass = function(element, name){
  126. if (!qq.hasClass(element, name)){
  127. element.className += ' ' + name;
  128. }
  129. };
  130. qq.removeClass = function(element, name){
  131. var re = new RegExp('(^| )' + name + '( |$)');
  132. element.className = element.className.replace(re, ' ').replace(/^\s+|\s+$/g, "");
  133. };
  134. qq.setText = function(element, text){
  135. element.innerText = text;
  136. element.textContent = text;
  137. };
  138. //
  139. // Selecting elements
  140. qq.children = function(element){
  141. var children = [],
  142. child = element.firstChild;
  143. while (child){
  144. if (child.nodeType == 1){
  145. children.push(child);
  146. }
  147. child = child.nextSibling;
  148. }
  149. return children;
  150. };
  151. qq.getByClass = function(element, className){
  152. if (element.querySelectorAll){
  153. return element.querySelectorAll('.' + className);
  154. }
  155. var result = [];
  156. var candidates = element.getElementsByTagName("*");
  157. var len = candidates.length;
  158. for (var i = 0; i < len; i++){
  159. if (qq.hasClass(candidates[i], className)){
  160. result.push(candidates[i]);
  161. }
  162. }
  163. return result;
  164. };
  165. /**
  166. * obj2url() takes a json-object as argument and generates
  167. * a querystring. pretty much like jQuery.param()
  168. *
  169. * how to use:
  170. *
  171. * `qq.obj2url({a:'b',c:'d'},'http://any.url/upload?otherParam=value');`
  172. *
  173. * will result in:
  174. *
  175. * `http://any.url/upload?otherParam=value&a=b&c=d`
  176. *
  177. * @param Object JSON-Object
  178. * @param String current querystring-part
  179. * @return String encoded querystring
  180. */
  181. qq.obj2url = function(obj, temp, prefixDone){
  182. var uristrings = [],
  183. prefix = '&',
  184. add = function(nextObj, i){
  185. var nextTemp = temp
  186. ? (/\[\]$/.test(temp)) // prevent double-encoding
  187. ? temp
  188. : temp+'['+i+']'
  189. : i;
  190. if ((nextTemp != 'undefined') && (i != 'undefined')) {
  191. uristrings.push(
  192. (typeof nextObj === 'object')
  193. ? qq.obj2url(nextObj, nextTemp, true)
  194. : (Object.prototype.toString.call(nextObj) === '[object Function]')
  195. ? encodeURIComponent(nextTemp) + '=' + encodeURIComponent(nextObj())
  196. : encodeURIComponent(nextTemp) + '=' + encodeURIComponent(nextObj)
  197. );
  198. }
  199. };
  200. if (!prefixDone && temp) {
  201. prefix = (/\?/.test(temp)) ? (/\?$/.test(temp)) ? '' : '&' : '?';
  202. uristrings.push(temp);
  203. uristrings.push(qq.obj2url(obj));
  204. } else if ((Object.prototype.toString.call(obj) === '[object Array]') && (typeof obj != 'undefined') ) {
  205. // we wont use a for-in-loop on an array (performance)
  206. for (var i = 0, len = obj.length; i < len; ++i){
  207. add(obj[i], i);
  208. }
  209. } else if ((typeof obj != 'undefined') && (obj !== null) && (typeof obj === "object")){
  210. // for anything else but a scalar, we will use for-in-loop
  211. for (var i in obj){
  212. add(obj[i], i);
  213. }
  214. } else {
  215. uristrings.push(encodeURIComponent(temp) + '=' + encodeURIComponent(obj));
  216. }
  217. return uristrings.join(prefix)
  218. .replace(/^&/, '')
  219. .replace(/%20/g, '+');
  220. };
  221. //
  222. //
  223. // Uploader Classes
  224. //
  225. //
  226. var qq = qq || {};
  227. /**
  228. * Creates upload button, validates upload, but doesn't create file list or dd.
  229. */
  230. qq.FileUploaderBasic = function(o){
  231. this._options = {
  232. // set to true to see the server response
  233. debug: false,
  234. action: '/server/upload',
  235. params: {},
  236. button: null,
  237. multiple: true,
  238. maxConnections: 3,
  239. method: 'POST',
  240. fieldName: 'qqfile',
  241. // validation
  242. allowedExtensions: [],
  243. sizeLimit: 0,
  244. minSizeLimit: 0,
  245. maxFilesCount: 0, // 0 - no limit, works only in multiple mode
  246. minFilesCount: 0, // 0 - no limit, works only in multiple mode
  247. // events
  248. // return false to cancel submit
  249. onSubmit: function(id, fileName){},
  250. onProgress: function(id, fileName, loaded, total){},
  251. onComplete: function(id, fileName, responseJSON){},
  252. onCancel: function(id, fileName){},
  253. // messages
  254. messages: {
  255. typeError: "{file} has invalid extension. Only {extensions} are allowed.",
  256. sizeError: "{file} is too large, maximum file size is {sizeLimit}.",
  257. minSizeError: "{file} is too small, minimum file size is {minSizeLimit}.",
  258. emptyError: "{file} is empty, please select files again without it.",
  259. onLeave: "The files are being uploaded, if you leave now the upload will be cancelled.",
  260. maxFilesError: "You must select less then {maxFilesCount} files.",
  261. minFilesError: "You must select more then {minFilesCount} files."
  262. },
  263. showMessage: function(message){
  264. alert(message);
  265. }
  266. };
  267. qq.extend(this._options, o);
  268. // number of files being uploaded
  269. this._filesInProgress = 0;
  270. // number of files was processed
  271. this._filesUploaded = 0;
  272. this._handler = this._createUploadHandler();
  273. if (this._options.button){
  274. this._button = this._createUploadButton(this._options.button);
  275. }
  276. this._preventLeaveInProgress();
  277. };
  278. qq.FileUploaderBasic.prototype = {
  279. setParams: function(params){
  280. this._options.params = params;
  281. },
  282. getInProgress: function(){
  283. return this._filesInProgress;
  284. },
  285. _createUploadButton: function(element){
  286. var self = this;
  287. return new qq.UploadButton({
  288. element: element,
  289. multiple: this._options.multiple && qq.UploadHandlerXhr.isSupported(),
  290. onChange: function(input){
  291. self._onInputChange(input);
  292. }
  293. });
  294. },
  295. _createUploadHandler: function(){
  296. var self = this,
  297. handlerClass;
  298. if(qq.UploadHandlerXhr.isSupported()){
  299. handlerClass = 'UploadHandlerXhr';
  300. } else {
  301. handlerClass = 'UploadHandlerForm';
  302. }
  303. var handler = new qq[handlerClass]({
  304. debug: this._options.debug,
  305. action: this._options.action,
  306. maxConnections: this._options.maxConnections,
  307. fieldName: this._options.fieldName,
  308. method: this._options.method,
  309. onProgress: function(id, fileName, loaded, total){
  310. self._onProgress(id, fileName, loaded, total);
  311. self._options.onProgress(id, fileName, loaded, total);
  312. },
  313. onComplete: function(id, fileName, result){
  314. self._onComplete(id, fileName, result);
  315. self._options.onComplete(id, fileName, result);
  316. },
  317. onCancel: function(id, fileName){
  318. self._onCancel(id, fileName);
  319. self._options.onCancel(id, fileName);
  320. }
  321. });
  322. return handler;
  323. },
  324. _preventLeaveInProgress: function(){
  325. var self = this;
  326. qq.attach(window, 'beforeunload', function(e){
  327. if (!self._filesInProgress){return;}
  328. var e = e || window.event;
  329. // for ie, ff
  330. e.returnValue = self._options.messages.onLeave;
  331. // for webkit
  332. return self._options.messages.onLeave;
  333. });
  334. },
  335. _onSubmit: function(id, fileName){
  336. this._filesInProgress++;
  337. },
  338. _onProgress: function(id, fileName, loaded, total){
  339. },
  340. _onComplete: function(id, fileName, result){
  341. this._filesInProgress--;
  342. if (result.error){
  343. this._options.showMessage(result.error);
  344. } else {
  345. this._filesUploaded++;
  346. }
  347. },
  348. _onCancel: function(id, fileName){
  349. this._filesInProgress--;
  350. },
  351. _onInputChange: function(input){
  352. if (this._handler instanceof qq.UploadHandlerXhr){
  353. this._uploadFileList(input.files);
  354. } else {
  355. if (this._validateFile(input)){
  356. this._uploadFile(input);
  357. }
  358. }
  359. this._button.reset();
  360. },
  361. _uploadFileList: function(files){
  362. if ( this._validateFiles(files) ) {
  363. for (var i=0; i<files.length; i++){
  364. this._uploadFile(files[i]);
  365. }
  366. }
  367. },
  368. _uploadFile: function(fileContainer){
  369. var id = this._handler.add(fileContainer);
  370. var fileName = this._handler.getName(id);
  371. if (this._options.onSubmit(id, fileName) !== false){
  372. this._onSubmit(id, fileName);
  373. this._handler.upload(id, this._options.params);
  374. }
  375. },
  376. _validateFiles: function(files){
  377. var uploadedCount = this._filesUploaded + files.length;
  378. if (this._options.maxFilesCount > 0) {
  379. if ( uploadedCount > this._options.maxFilesCount) {
  380. this._error('maxFilesError', 'name');
  381. return false;
  382. }
  383. }
  384. if (this._options.minFilesCount > 0) {
  385. if ( uploadedCount < this._options.minFilesCount) {
  386. this._error('minFilesError', 'name');
  387. return false;
  388. }
  389. }
  390. for (var i=0; i<files.length; i++){
  391. if ( !this._validateFile(files[i])){
  392. return false;
  393. }
  394. }
  395. return true;
  396. },
  397. _validateFile: function(file){
  398. var name, size;
  399. if (file.value){
  400. // it is a file input
  401. // get input value and remove path to normalize
  402. name = file.value.replace(/.*(\/|\\)/, "");
  403. } else {
  404. // fix missing properties in Safari
  405. name = file.fileName != null ? file.fileName : file.name;
  406. size = file.fileSize != null ? file.fileSize : file.size;
  407. }
  408. if (! this._isAllowedExtension(name)){
  409. this._error('typeError', name);
  410. return false;
  411. } else if (size === 0){
  412. this._error('emptyError', name);
  413. return false;
  414. } else if (size && this._options.sizeLimit && size > this._options.sizeLimit){
  415. this._error('sizeError', name);
  416. return false;
  417. } else if (size && size < this._options.minSizeLimit){
  418. this._error('minSizeError', name);
  419. return false;
  420. }
  421. return true;
  422. },
  423. _error: function(code, fileName){
  424. var message = this._options.messages[code];
  425. function r(name, replacement){ message = message.replace(name, replacement); }
  426. r('{file}', this._formatFileName(fileName));
  427. r('{extensions}', this._options.allowedExtensions.join(', '));
  428. r('{sizeLimit}', this._formatSize(this._options.sizeLimit));
  429. r('{minSizeLimit}', this._formatSize(this._options.minSizeLimit));
  430. r('{maxFilesCount}', this._options.maxFilesCount);
  431. r('{minFilesCount}', this._options.minFilesCount);
  432. this._options.showMessage(message);
  433. },
  434. _formatFileName: function(name){
  435. if (name.length > 33){
  436. name = name.slice(0, 19) + '...' + name.slice(-13);
  437. }
  438. return name;
  439. },
  440. _isAllowedExtension: function(fileName){
  441. var ext = (-1 !== fileName.indexOf('.')) ? fileName.replace(/.*[.]/, '').toLowerCase() : '';
  442. var allowed = this._options.allowedExtensions;
  443. if (!allowed.length){return true;}
  444. for (var i=0; i<allowed.length; i++){
  445. if (allowed[i].toLowerCase() == ext){ return true;}
  446. }
  447. return false;
  448. },
  449. _formatSize: function(bytes){
  450. var i = -1;
  451. do {
  452. bytes = bytes / 1024;
  453. i++;
  454. } while (bytes > 99);
  455. return Math.max(bytes, 0.1).toFixed(1) + ['kB', 'MB', 'GB', 'TB', 'PB', 'EB'][i];
  456. }
  457. };
  458. /**
  459. * Class that creates upload widget with drag-and-drop and file list
  460. * @inherits qq.FileUploaderBasic
  461. */
  462. qq.FileUploader = function(o){
  463. // call parent constructor
  464. qq.FileUploaderBasic.apply(this, arguments);
  465. // additional options
  466. qq.extend(this._options, {
  467. element: null,
  468. // if set, will be used instead of qq-upload-list in template
  469. listElement: null,
  470. template: '<div class="qq-uploader">' +
  471. '<div class="qq-upload-drop-area"><span>Drop files here to upload</span></div>' +
  472. '<div class="qq-upload-button">Upload a file</div>' +
  473. '<ul class="qq-upload-list"></ul>' +
  474. '</div>',
  475. // template for one item in file list
  476. fileTemplate: '<li>' +
  477. '<span class="qq-upload-file"></span>' +
  478. '<span class="qq-upload-spinner"></span>' +
  479. '<span class="qq-upload-size"></span>' +
  480. '<a class="qq-upload-cancel" href="#">Cancel</a>' +
  481. '<span class="qq-upload-failed-text">Failed</span>' +
  482. '</li>',
  483. classes: {
  484. // used to get elements from templates
  485. button: 'qq-upload-button',
  486. drop: 'qq-upload-drop-area',
  487. dropActive: 'qq-upload-drop-area-active',
  488. list: 'qq-upload-list',
  489. file: 'qq-upload-file',
  490. spinner: 'qq-upload-spinner',
  491. size: 'qq-upload-size',
  492. cancel: 'qq-upload-cancel',
  493. // added to list item when upload completes
  494. // used in css to hide progress spinner
  495. success: 'qq-upload-success',
  496. fail: 'qq-upload-fail'
  497. }
  498. });
  499. // overwrite options with user supplied
  500. qq.extend(this._options, o);
  501. this._element = this._options.element;
  502. this._element.innerHTML = this._options.template;
  503. this._listElement = this._options.listElement || this._find(this._element, 'list');
  504. this._classes = this._options.classes;
  505. this._button = this._createUploadButton(this._find(this._element, 'button'));
  506. this._bindCancelEvent();
  507. this._setupDragDrop();
  508. };
  509. // inherit from Basic Uploader
  510. qq.extend(qq.FileUploader.prototype, qq.FileUploaderBasic.prototype);
  511. qq.extend(qq.FileUploader.prototype, {
  512. /**
  513. * Gets one of the elements listed in this._options.classes
  514. **/
  515. _find: function(parent, type){
  516. var element = qq.getByClass(parent, this._options.classes[type])[0];
  517. if (!element){
  518. throw new Error('element not found ' + type);
  519. }
  520. return element;
  521. },
  522. _setupDragDrop: function(){
  523. var self = this,
  524. dropArea = this._find(this._element, 'drop');
  525. var dz = new qq.UploadDropZone({
  526. element: dropArea,
  527. onEnter: function(e){
  528. qq.addClass(dropArea, self._classes.dropActive);
  529. e.stopPropagation();
  530. },
  531. onLeave: function(e){
  532. e.stopPropagation();
  533. },
  534. onLeaveNotDescendants: function(e){
  535. qq.removeClass(dropArea, self._classes.dropActive);
  536. },
  537. onDrop: function(e){
  538. dropArea.style.display = 'none';
  539. qq.removeClass(dropArea, self._classes.dropActive);
  540. self._uploadFileList(e.dataTransfer.files);
  541. }
  542. });
  543. dropArea.style.display = 'none';
  544. qq.attach(document, 'dragenter', function(e){
  545. if (!dz._isValidFileDrag(e)) return;
  546. dropArea.style.display = 'block';
  547. });
  548. qq.attach(document, 'dragleave', function(e){
  549. if (!dz._isValidFileDrag(e)) return;
  550. var relatedTarget = document.elementFromPoint(e.clientX, e.clientY);
  551. // only fire when leaving document out
  552. if ( ! relatedTarget || relatedTarget.nodeName == "HTML"){
  553. dropArea.style.display = 'none';
  554. }
  555. });
  556. },
  557. _onSubmit: function(id, fileName){
  558. qq.FileUploaderBasic.prototype._onSubmit.apply(this, arguments);
  559. this._addToList(id, fileName);
  560. },
  561. _onProgress: function(id, fileName, loaded, total){
  562. qq.FileUploaderBasic.prototype._onProgress.apply(this, arguments);
  563. var item = this._getItemByFileId(id);
  564. var size = this._find(item, 'size');
  565. size.style.display = 'inline';
  566. var text;
  567. if (loaded != total){
  568. text = Math.round(loaded / total * 100) + '% from ' + this._formatSize(total);
  569. } else {
  570. text = this._formatSize(total);
  571. }
  572. qq.setText(size, text);
  573. },
  574. _onComplete: function(id, fileName, result){
  575. qq.FileUploaderBasic.prototype._onComplete.apply(this, arguments);
  576. // mark completed
  577. var item = this._getItemByFileId(id);
  578. qq.remove(this._find(item, 'cancel'));
  579. qq.remove(this._find(item, 'spinner'));
  580. if (result.success){
  581. qq.addClass(item, this._classes.success);
  582. } else {
  583. qq.addClass(item, this._classes.fail);
  584. }
  585. },
  586. _addToList: function(id, fileName){
  587. var item = qq.toElement(this._options.fileTemplate);
  588. item.qqFileId = id;
  589. var fileElement = this._find(item, 'file');
  590. qq.setText(fileElement, this._formatFileName(fileName));
  591. this._find(item, 'size').style.display = 'none';
  592. this._listElement.appendChild(item);
  593. },
  594. _getItemByFileId: function(id){
  595. var item = this._listElement.firstChild;
  596. // there can't be txt nodes in dynamically created list
  597. // and we can use nextSibling
  598. while (item){
  599. if (item.qqFileId == id) return item;
  600. item = item.nextSibling;
  601. }
  602. },
  603. /**
  604. * delegate click event for cancel link
  605. **/
  606. _bindCancelEvent: function(){
  607. var self = this,
  608. list = this._listElement;
  609. qq.attach(list, 'click', function(e){
  610. e = e || window.event;
  611. var target = e.target || e.srcElement;
  612. if (qq.hasClass(target, self._classes.cancel)){
  613. qq.preventDefault(e);
  614. var item = target.parentNode;
  615. self._handler.cancel(item.qqFileId);
  616. qq.remove(item);
  617. }
  618. });
  619. }
  620. });
  621. qq.UploadDropZone = function(o){
  622. this._options = {
  623. element: null,
  624. onEnter: function(e){},
  625. onLeave: function(e){},
  626. // is not fired when leaving element by hovering descendants
  627. onLeaveNotDescendants: function(e){},
  628. onDrop: function(e){}
  629. };
  630. qq.extend(this._options, o);
  631. this._element = this._options.element;
  632. this._disableDropOutside();
  633. this._attachEvents();
  634. };
  635. qq.UploadDropZone.prototype = {
  636. _disableDropOutside: function(e){
  637. // run only once for all instances
  638. if (!qq.UploadDropZone.dropOutsideDisabled ){
  639. qq.attach(document, 'dragover', function(e){
  640. if (e.dataTransfer){
  641. e.dataTransfer.dropEffect = 'none';
  642. e.preventDefault();
  643. }
  644. });
  645. qq.UploadDropZone.dropOutsideDisabled = true;
  646. }
  647. },
  648. _attachEvents: function(){
  649. var self = this;
  650. qq.attach(self._element, 'dragover', function(e){
  651. if (!self._isValidFileDrag(e)) return;
  652. var effect = e.dataTransfer.effectAllowed;
  653. if (effect == 'move' || effect == 'linkMove'){
  654. e.dataTransfer.dropEffect = 'move'; // for FF (only move allowed)
  655. } else {
  656. e.dataTransfer.dropEffect = 'copy'; // for Chrome
  657. }
  658. e.stopPropagation();
  659. e.preventDefault();
  660. });
  661. qq.attach(self._element, 'dragenter', function(e){
  662. if (!self._isValidFileDrag(e)) return;
  663. self._options.onEnter(e);
  664. });
  665. qq.attach(self._element, 'dragleave', function(e){
  666. if (!self._isValidFileDrag(e)) return;
  667. self._options.onLeave(e);
  668. var relatedTarget = document.elementFromPoint(e.clientX, e.clientY);
  669. // do not fire when moving a mouse over a descendant
  670. if (qq.contains(this, relatedTarget)) return;
  671. self._options.onLeaveNotDescendants(e);
  672. });
  673. qq.attach(self._element, 'drop', function(e){
  674. if (!self._isValidFileDrag(e)) return;
  675. e.preventDefault();
  676. self._options.onDrop(e);
  677. });
  678. },
  679. _isValidFileDrag: function(e){
  680. var dt = e.dataTransfer,
  681. // do not check dt.types.contains in webkit, because it crashes safari 4
  682. isWebkit = navigator.userAgent.indexOf("AppleWebKit") > -1;
  683. // dt.effectAllowed is none in Safari 5
  684. // dt.types.contains check is for firefox
  685. return dt && dt.effectAllowed != 'none' &&
  686. (dt.files || (!isWebkit && dt.types.contains && dt.types.contains('Files')));
  687. }
  688. };
  689. qq.UploadButton = function(o){
  690. this._options = {
  691. element: null,
  692. // if set to true adds multiple attribute to file input
  693. multiple: false,
  694. // name attribute of file input
  695. name: 'file',
  696. onChange: function(input){},
  697. hoverClass: 'qq-upload-button-hover',
  698. focusClass: 'qq-upload-button-focus'
  699. };
  700. qq.extend(this._options, o);
  701. this._element = this._options.element;
  702. // make button suitable container for input
  703. qq.css(this._element, {
  704. position: 'relative',
  705. overflow: 'hidden',
  706. // Make sure browse button is in the right side
  707. // in Internet Explorer
  708. direction: 'ltr'
  709. });
  710. this._input = this._createInput();
  711. };
  712. qq.UploadButton.prototype = {
  713. /* returns file input element */
  714. getInput: function(){
  715. return this._input;
  716. },
  717. /* cleans/recreates the file input */
  718. reset: function(){
  719. if (this._input.parentNode){
  720. qq.remove(this._input);
  721. }
  722. qq.removeClass(this._element, this._options.focusClass);
  723. this._input = this._createInput();
  724. },
  725. _createInput: function(){
  726. var input = document.createElement("input");
  727. if (this._options.multiple){
  728. input.setAttribute("multiple", "multiple");
  729. }
  730. input.setAttribute("type", "file");
  731. input.setAttribute("name", this._options.name);
  732. qq.css(input, {
  733. position: 'absolute',
  734. // in Opera only 'browse' button
  735. // is clickable and it is located at
  736. // the right side of the input
  737. right: 0,
  738. top: 0,
  739. fontFamily: 'Arial',
  740. // 4 persons reported this, the max values that worked for them were 243, 236, 236, 118
  741. fontSize: '118px',
  742. margin: 0,
  743. padding: 0,
  744. cursor: 'pointer',
  745. opacity: 0
  746. });
  747. this._element.appendChild(input);
  748. var self = this;
  749. qq.attach(input, 'change', function(){
  750. self._options.onChange(input);
  751. });
  752. qq.attach(input, 'mouseover', function(){
  753. qq.addClass(self._element, self._options.hoverClass);
  754. });
  755. qq.attach(input, 'mouseout', function(){
  756. qq.removeClass(self._element, self._options.hoverClass);
  757. });
  758. qq.attach(input, 'focus', function(){
  759. qq.addClass(self._element, self._options.focusClass);
  760. });
  761. qq.attach(input, 'blur', function(){
  762. qq.removeClass(self._element, self._options.focusClass);
  763. });
  764. // IE and Opera, unfortunately have 2 tab stops on file input
  765. // which is unacceptable in our case, disable keyboard access
  766. if (window.attachEvent){
  767. // it is IE or Opera
  768. input.setAttribute('tabIndex', "-1");
  769. }
  770. return input;
  771. }
  772. };
  773. /**
  774. * Class for uploading files, uploading itself is handled by child classes
  775. */
  776. qq.UploadHandlerAbstract = function(o){
  777. this._options = {
  778. debug: false,
  779. action: '/upload.php',
  780. method: 'POST',
  781. fieldName: 'qqfile',
  782. // maximum number of concurrent uploads
  783. maxConnections: 999,
  784. onProgress: function(id, fileName, loaded, total){},
  785. onComplete: function(id, fileName, response){},
  786. onCancel: function(id, fileName){}
  787. };
  788. qq.extend(this._options, o);
  789. this._queue = [];
  790. // params for files in queue
  791. this._params = [];
  792. };
  793. qq.UploadHandlerAbstract.prototype = {
  794. log: function(str){
  795. if (this._options.debug && window.console) console.log('[uploader] ' + str);
  796. },
  797. /**
  798. * Adds file or file input to the queue
  799. * @returns id
  800. **/
  801. add: function(file){},
  802. /**
  803. * Sends the file identified by id and additional query params to the server
  804. */
  805. upload: function(id, params){
  806. var len = this._queue.push(id);
  807. var copy = {};
  808. qq.extend(copy, params);
  809. this._params[id] = copy;
  810. // if too many active uploads, wait...
  811. if (len <= this._options.maxConnections){
  812. this._upload(id, this._params[id]);
  813. }
  814. },
  815. /**
  816. * Cancels file upload by id
  817. */
  818. cancel: function(id){
  819. this._cancel(id);
  820. this._dequeue(id);
  821. },
  822. /**
  823. * Cancells all uploads
  824. */
  825. cancelAll: function(){
  826. for (var i=0; i<this._queue.length; i++){
  827. this._cancel(this._queue[i]);
  828. }
  829. this._queue = [];
  830. },
  831. /**
  832. * Returns name of the file identified by id
  833. */
  834. getName: function(id){},
  835. /**
  836. * Returns size of the file identified by id
  837. */
  838. getSize: function(id){},
  839. /**
  840. * Returns id of files being uploaded or
  841. * waiting for their turn
  842. */
  843. getQueue: function(){
  844. return this._queue;
  845. },
  846. /**
  847. * Actual upload method
  848. */
  849. _upload: function(id){},
  850. /**
  851. * Actual cancel method
  852. */
  853. _cancel: function(id){},
  854. /**
  855. * Removes element from queue, starts upload of next
  856. */
  857. _dequeue: function(id){
  858. var i = qq.indexOf(this._queue, id);
  859. this._queue.splice(i, 1);
  860. var max = this._options.maxConnections;
  861. if (this._queue.length >= max && i < max){
  862. var nextId = this._queue[max-1];
  863. this._upload(nextId, this._params[nextId]);
  864. }
  865. }
  866. };
  867. /**
  868. * Class for uploading files using form and iframe
  869. * @inherits qq.UploadHandlerAbstract
  870. */
  871. qq.UploadHandlerForm = function(o){
  872. qq.UploadHandlerAbstract.apply(this, arguments);
  873. this._inputs = {};
  874. };
  875. // @inherits qq.UploadHandlerAbstract
  876. qq.extend(qq.UploadHandlerForm.prototype, qq.UploadHandlerAbstract.prototype);
  877. qq.extend(qq.UploadHandlerForm.prototype, {
  878. add: function(fileInput){
  879. fileInput.setAttribute('name', this._options.fieldName);
  880. var id = 'qq-upload-handler-iframe' + qq.getUniqueId();
  881. this._inputs[id] = fileInput;
  882. // remove file input from DOM
  883. if (fileInput.parentNode){
  884. qq.remove(fileInput);
  885. }
  886. return id;
  887. },
  888. getName: function(id){
  889. // get input value and remove path to normalize
  890. return this._inputs[id].value.replace(/.*(\/|\\)/, "");
  891. },
  892. _cancel: function(id){
  893. this._options.onCancel(id, this.getName(id));
  894. delete this._inputs[id];
  895. var iframe = document.getElementById(id);
  896. if (iframe){
  897. // to cancel request set src to something else
  898. // we use src="javascript:false;" because it doesn't
  899. // trigger ie6 prompt on https
  900. iframe.setAttribute('src', 'javascript:false;');
  901. qq.remove(iframe);
  902. }
  903. },
  904. _upload: function(id, params){
  905. var input = this._inputs[id];
  906. if (!input){
  907. throw new Error('file with passed id was not added, or already uploaded or cancelled');
  908. }
  909. var fileName = this.getName(id);
  910. var iframe = this._createIframe(id);
  911. var form = this._createForm(iframe, params);
  912. form.appendChild(input);
  913. var self = this;
  914. this._attachLoadEvent(iframe, function(){
  915. self.log('iframe loaded');
  916. var response = self._getIframeContentJSON(iframe);
  917. self._options.onComplete(id, fileName, response);
  918. self._dequeue(id);
  919. delete self._inputs[id];
  920. // timeout added to fix busy state in FF3.6
  921. setTimeout(function(){
  922. qq.remove(iframe);
  923. }, 1);
  924. });
  925. form.submit();
  926. qq.remove(form);
  927. return id;
  928. },
  929. _attachLoadEvent: function(iframe, callback){
  930. qq.attach(iframe, 'load', function(){
  931. // when we remove iframe from dom
  932. // the request stops, but in IE load
  933. // event fires
  934. if (!iframe.parentNode){
  935. return;
  936. }
  937. // fixing Opera 10.53
  938. if (iframe.contentDocument &&
  939. iframe.contentDocument.body &&
  940. iframe.contentDocument.body.innerHTML == "false"){
  941. // In Opera event is fired second time
  942. // when body.innerHTML changed from false
  943. // to server response approx. after 1 sec
  944. // when we upload file with iframe
  945. return;
  946. }
  947. callback();
  948. });
  949. },
  950. /**
  951. * Returns json object received by iframe from server.
  952. */
  953. _getIframeContentJSON: function(iframe){
  954. // iframe.contentWindow.document - for IE<7
  955. var doc = iframe.contentDocument ? iframe.contentDocument: iframe.contentWindow.document,
  956. response;
  957. this.log("converting iframe's innerHTML to JSON");
  958. this.log("innerHTML = " + doc.body.innerHTML);
  959. try {
  960. response = eval("(" + doc.body.innerHTML + ")");
  961. } catch(err){
  962. response = {};
  963. }
  964. return response;
  965. },
  966. /**
  967. * Creates iframe with unique name
  968. */
  969. _createIframe: function(id){
  970. // We can't use following code as the name attribute
  971. // won't be properly registered in IE6, and new window
  972. // on form submit will open
  973. // var iframe = document.createElement('iframe');
  974. // iframe.setAttribute('name', id);
  975. var iframe = qq.toElement('<iframe src="javascript:false;" name="' + id + '" />');
  976. // src="javascript:false;" removes ie6 prompt on https
  977. iframe.setAttribute('id', id);
  978. iframe.style.display = 'none';
  979. document.body.appendChild(iframe);
  980. return iframe;
  981. },
  982. /**
  983. * Creates form, that will be submitted to iframe
  984. */
  985. _createForm: function(iframe, params){
  986. // We can't use the following code in IE6
  987. // var form = document.createElement('form');
  988. // form.setAttribute('method', 'post');
  989. // form.setAttribute('enctype', 'multipart/form-data');
  990. // Because in this case file won't be attached to request
  991. var form = qq.toElement('<form enctype="multipart/form-data"></form>');
  992. var queryString = qq.obj2url(params, this._options.action);
  993. form.setAttribute('method', this._options.method);
  994. form.setAttribute('action', queryString);
  995. form.setAttribute('target', iframe.name);
  996. form.style.display = 'none';
  997. // Rails CSRFProtection
  998. var token = $('meta[name="csrf-token"]').attr('content');
  999. var param = $('meta[name="csrf-param"]').attr('content');
  1000. var input = qq.toElement('<input type="hidden" />');
  1001. input.setAttribute('name', param);
  1002. input.setAttribute('value', token);
  1003. form.appendChild(input);
  1004. document.body.appendChild(form);
  1005. return form;
  1006. }
  1007. });
  1008. /**
  1009. * Class for uploading files using xhr
  1010. * @inherits qq.UploadHandlerAbstract
  1011. */
  1012. qq.UploadHandlerXhr = function(o){
  1013. qq.UploadHandlerAbstract.apply(this, arguments);
  1014. this._files = [];
  1015. this._xhrs = [];
  1016. // current loaded size in bytes for each file
  1017. this._loaded = [];
  1018. };
  1019. // static method
  1020. qq.UploadHandlerXhr.isSupported = function(){
  1021. var input = document.createElement('input');
  1022. input.type = 'file';
  1023. return (
  1024. 'multiple' in input &&
  1025. typeof File != "undefined" &&
  1026. typeof (new XMLHttpRequest()).upload != "undefined" );
  1027. };
  1028. // @inherits qq.UploadHandlerAbstract
  1029. qq.extend(qq.UploadHandlerXhr.prototype, qq.UploadHandlerAbstract.prototype)
  1030. qq.extend(qq.UploadHandlerXhr.prototype, {
  1031. /**
  1032. * Adds file to the queue
  1033. * Returns id to use with upload, cancel
  1034. **/
  1035. add: function(file){
  1036. if (!(file instanceof File)){
  1037. throw new Error('Passed obj in not a File (in qq.UploadHandlerXhr)');
  1038. }
  1039. return this._files.push(file) - 1;
  1040. },
  1041. getName: function(id){
  1042. var file = this._files[id];
  1043. // fix missing name in Safari 4
  1044. return file.fileName != null ? file.fileName : file.name;
  1045. },
  1046. getSize: function(id){
  1047. var file = this._files[id];
  1048. return file.fileSize != null ? file.fileSize : file.size;
  1049. },
  1050. /**
  1051. * Returns uploaded bytes for file identified by id
  1052. */
  1053. getLoaded: function(id){
  1054. return this._loaded[id] || 0;
  1055. },
  1056. /**
  1057. * Sends the file identified by id and additional query params to the server
  1058. * @param {Object} params name-value string pairs
  1059. */
  1060. _upload: function(id, params){
  1061. var file = this._files[id],
  1062. name = this.getName(id),
  1063. size = this.getSize(id);
  1064. this._loaded[id] = 0;
  1065. var xhr = this._xhrs[id] = new XMLHttpRequest();
  1066. var self = this;
  1067. xhr.upload.onprogress = function(e){
  1068. if (e.lengthComputable){
  1069. self._loaded[id] = e.loaded;
  1070. self._options.onProgress(id, name, e.loaded, e.total);
  1071. }
  1072. };
  1073. xhr.onreadystatechange = function(){
  1074. if (xhr.readyState == 4){
  1075. self._onComplete(id, xhr);
  1076. }
  1077. };
  1078. // build query string
  1079. params = params || {};
  1080. params[this._options.fieldName] = name;
  1081. var queryString = qq.obj2url(params, this._options.action);
  1082. xhr.open(this._options.method, queryString, true);
  1083. xhr.setRequestHeader("X-Requested-With", "XMLHttpRequest");
  1084. xhr.setRequestHeader("X-File-Name", encodeURIComponent(name));
  1085. xhr.setRequestHeader('X-File-Size', size);
  1086. xhr.setRequestHeader('X-File-Type', file.type);
  1087. xhr.setRequestHeader("Content-Type", "application/octet-stream");
  1088. // Rails CSRFProtection
  1089. if ($.rails) $.rails.CSRFProtection(xhr);
  1090. xhr.send(file);
  1091. },
  1092. _onComplete: function(id, xhr){
  1093. // the request was aborted/cancelled
  1094. if (!this._files[id]) return;
  1095. var name = this.getName(id);
  1096. var size = this.getSize(id);
  1097. this._options.onProgress(id, name, size, size);
  1098. if ([200, 201].indexOf( xhr.status ) > -1){
  1099. this.log("xhr - server response received");
  1100. this.log("responseText = " + xhr.responseText);
  1101. var response;
  1102. try {
  1103. response = eval("(" + xhr.responseText + ")");
  1104. } catch(err){
  1105. response = {};
  1106. }
  1107. this._options.onComplete(id, name, response);
  1108. } else {
  1109. this._options.onComplete(id, name, {});
  1110. }
  1111. this._files[id] = null;
  1112. this._xhrs[id] = null;
  1113. this._dequeue(id);
  1114. },
  1115. _cancel: function(id){
  1116. this._options.onCancel(id, this.getName(id));
  1117. this._files[id] = null;
  1118. if (this._xhrs[id]){
  1119. this._xhrs[id].abort();
  1120. this._xhrs[id] = null;
  1121. }
  1122. }
  1123. });
  1124. /**
  1125. * Author: Sergey Kukunin
  1126. * See: https://github.com/Kukunin/jquery-endless-scroll
  1127. */
  1128. (function($) {
  1129. 'use strict';
  1130. // Is pushState supported by this browser?
  1131. $.support.pushState =
  1132. window.history && window.history.pushState && window.history.replaceState &&
  1133. // pushState isn't reliable on iOS until 5.
  1134. !navigator.userAgent.match(/((iPod|iPhone|iPad).+\bOS\s+[1-4]|WebApps\/.+CFNetwork)/)
  1135. //Declaration of modules
  1136. var scrollModule = {
  1137. init: function(options, obj) {
  1138. obj.options = $.extend({
  1139. scrollContainer: window,
  1140. scrollPadding: 100,
  1141. scrollEventDelay: 300
  1142. }, options);
  1143. this.options = obj.options;
  1144. this.container = obj.container;
  1145. obj.scrollModule = this;
  1146. this._toplock = true;
  1147. this._bottomlock = true;
  1148. this.scrollContainer = $(this.options.scrollContainer);
  1149. this.updateEntities();
  1150. this.sortMarkers();
  1151. this.currentPage = null;
  1152. this.container.on("jes:afterPageLoad", $.proxy(function(event, url, placement) {
  1153. this.updateEntities();
  1154. this.sortMarkers();
  1155. this.checkMarker();
  1156. if ( placement == "top" ) {
  1157. //Get offset between first and second pages
  1158. var offset = this.markers[1].top,
  1159. scrollTop = this.scrollContainer.scrollTop();
  1160. this.scrollContainer.scrollTop(scrollTop + offset);
  1161. }
  1162. }, this));
  1163. this.bind();
  1164. },
  1165. updateEntities: function() {
  1166. this.entities = $(this.options.entity, this.container);
  1167. },
  1168. sortMarkers: function() {
  1169. var markers = [];
  1170. $(".jes-marker", this.container).each(function() {
  1171. markers.push({ top: $(this).position().top, url: $(this).data("jesUrl") });
  1172. });
  1173. this.markers = markers;
  1174. },
  1175. //Check, whether user scrolled to another page
  1176. checkMarker: function() {
  1177. var newPage = this.markers[0],
  1178. scrollTop = this.scrollContainer.scrollTop();
  1179. //Determine, what is current page
  1180. for ( var i = 1; i < this.markers.length; i++ ) {
  1181. if ( scrollTop > this.markers[i].top ) {
  1182. newPage = this.markers[i];
  1183. } else {
  1184. break;
  1185. }
  1186. }
  1187. if ( newPage.url != this.currentPage ) {
  1188. this.currentPage = newPage.url;
  1189. $(this.container).trigger("jes:scrollToPage", newPage.url);
  1190. }
  1191. },
  1192. bind: function() {
  1193. this.scrollContainer.on("scroll.jes", $.proxy(function(event) {
  1194. if ( this._tId ) { return; }
  1195. this.scrollHandler(event);
  1196. //Clean up mark
  1197. this._tId= setTimeout($.proxy(function(){
  1198. this._tId = null;
  1199. },this), this.options.scrollEventDelay);
  1200. }, this));
  1201. },
  1202. unbind: function() {
  1203. $(this.options.scrollContainer).off("scroll.jes");
  1204. },
  1205. scrollHandler: function(ev) {
  1206. var $scrollable = this.scrollContainer,
  1207. $entities = this.entities,
  1208. $firstEntity = $entities.first(),
  1209. $lastEntity = $entities.last();
  1210. var scrollTop = $scrollable.scrollTop(),
  1211. height = $scrollable.height(),
  1212. scrollBottom = scrollTop + height;
  1213. var topEntityPosition = $firstEntity.position().top,
  1214. topTarget = topEntityPosition + this.options.scrollPadding,
  1215. bottomEntityPosition = $lastEntity.outerHeight() + $lastEntity.position().top,
  1216. bottomTarget = bottomEntityPosition - this.options.scrollPadding;
  1217. //Don't trigger event again, if already fired
  1218. //Visitor have to leave the area and get back to fire event again
  1219. //Process top threshold
  1220. if ( scrollTop < topTarget ) {
  1221. if ( !this._toplock ) {
  1222. $(this.container).trigger("jes:topThreshold");
  1223. this._toplock = true;
  1224. }
  1225. } else {
  1226. this._toplock = false;
  1227. }
  1228. //Process bottom threshold
  1229. if ( scrollBottom > bottomTarget ) {
  1230. if ( !this._bottomlock ) {
  1231. $(this.container).trigger("jes:bottomThreshold");
  1232. this._bottomlock = true;
  1233. }
  1234. } else {
  1235. this._bottomlock = false;
  1236. }
  1237. this.checkMarker();
  1238. }
  1239. }
  1240. var ajaxModule = {
  1241. init: function(options, obj) {
  1242. obj.options = $.extend({
  1243. }, options);
  1244. this.options = obj.options;
  1245. this.container = obj.container;
  1246. //markers
  1247. this.setMarker($(this.options.entity, this.container).first(), location.href);
  1248. obj.ajaxModule = this;
  1249. },
  1250. loadPage: function(url, placement, callback) {
  1251. //The hash with methods list
  1252. //depends from placement
  1253. var actions = {
  1254. top: {
  1255. find: 'first',
  1256. inject: 'insertBefore'
  1257. },
  1258. bottom: {
  1259. find: 'last',
  1260. inject: 'insertAfter'
  1261. }
  1262. },
  1263. action = actions[placement];
  1264. this.container.trigger("jes:beforePageLoad", [url, placement]);
  1265. //Make AJAX query
  1266. $.get(url, null, $.proxy(function (_data) {
  1267. var data = $("<div>").html(_data),
  1268. containerSelector = this.options.container,
  1269. container = $(containerSelector, data).first();
  1270. if ( container.length ) {
  1271. //Find entities
  1272. var entities = container.find(this.options.entity);
  1273. if ( placement == "bottom" ) {
  1274. //Remove duplicated (staled) entities from page
  1275. entities.each(function(i) {
  1276. var id = $(this).attr("id");
  1277. if ( id ) {
  1278. $('#' + id, this.container).remove();
  1279. }
  1280. });
  1281. }
  1282. //Find the cursor
  1283. var cursor = $(this.options.entity, containerSelector)[action.find]();
  1284. //Find and insert entities
  1285. entities[action.inject](cursor);
  1286. this.setMarker(entities.first(), url);
  1287. }
  1288. if ( $.isFunction(callback) ) {
  1289. callback(data);
  1290. }
  1291. this.container.trigger("jes:afterPageLoad", [url, placement, data, entities]);
  1292. }, this), 'html');
  1293. },
  1294. setMarker: function(entity, url) {
  1295. entity.addClass("jes-marker").data("jesUrl", url);
  1296. }
  1297. }
  1298. var navigationModule = {
  1299. init: function(options, obj) {
  1300. obj.options = $.extend({
  1301. nextPage: ".pagination a[rel=next]",
  1302. previousPage: ".pagination a[rel=previous]"
  1303. }, options);
  1304. this.options = obj.options;
  1305. this.container = obj.container;
  1306. $.each([{
  1307. selector: this.options.nextPage,
  1308. event: "jes:bottomThreshold.navigation",
  1309. placement: 'bottom'
  1310. }, {
  1311. selector: this.options.previousPage,
  1312. event: "jes:topThreshold.navigation",
  1313. placement: 'top'
  1314. }], $.proxy(function(i, v) {
  1315. v.element = $(v.selector);
  1316. if ( v.element.length ) {
  1317. var url = v.element.prop("href"),
  1318. lock = false;
  1319. var handler = function() {
  1320. //this object is container
  1321. if ( lock || !url ) return;
  1322. lock = true;
  1323. obj.ajaxModule.loadPage(url, v.placement, $.proxy(function( data ) {
  1324. //Search new next link
  1325. var newElement = $(v.selector, $(data));
  1326. if ( newElement.length ) {
  1327. //Update URL and remove lock
  1328. lock = false;
  1329. url = newElement.prop("href");
  1330. v.element.attr("href", url);
  1331. } else {
  1332. //Remove event at all
  1333. $(this).off(v.event);
  1334. v.element.remove();
  1335. }
  1336. }, this));
  1337. };
  1338. $(this.container).on(v.event, handler);
  1339. $(v.element).on("click", $.proxy(function(ev) {
  1340. ev.preventDefault();
  1341. handler.apply(this.container)
  1342. }, this));
  1343. }
  1344. },this));
  1345. }
  1346. }
  1347. var pushStateHistory = {
  1348. init: function(options, obj) {
  1349. if ( !$.support.pushState ) {
  1350. return;
  1351. }
  1352. obj.container.on("jes:scrollToPage", function(event, url) {
  1353. history.replaceState({}, null, url);
  1354. });
  1355. }
  1356. }
  1357. $.endlessScroll = function(options) {
  1358. //Initialize modules
  1359. this.options = $.extend(true, {
  1360. container: "#container",
  1361. entity: ".entity",
  1362. _modules: [ ajaxModule, scrollModule, navigationModule, pushStateHistory ],
  1363. modules: []
  1364. }, options);
  1365. this.container = $(this.options.container);
  1366. if ( !this.container.length ) {
  1367. throw "Container for endless scroll isn't available on the page";
  1368. return;
  1369. }
  1370. //Merge custom options with default
  1371. $.merge(this.options.modules, this.options._modules);
  1372. //Init modules
  1373. this.options.modules.forEach($.proxy(function(module) {
  1374. module.init(this.options, this);
  1375. },this));
  1376. return this;
  1377. }
  1378. })(jQuery);
  1379. (function($, undefined) {
  1380. /**
  1381. * Unobtrusive scripting adapter for jQuery
  1382. *
  1383. * Requires jQuery 1.6.0 or later.
  1384. * https://github.com/rails/jquery-ujs
  1385. * Uploading file using rails.js
  1386. * =============================
  1387. *
  1388. * By default, browsers do not allow files to be uploaded via AJAX. As a result, if there are any non-blank file fields
  1389. * in the remote form, this adapter aborts the AJAX submission and allows the form to submit through standard means.
  1390. *
  1391. * The `ajax:aborted:file` event allows you to bind your own handler to process the form submission however you wish.
  1392. *
  1393. * Ex:
  1394. * $('form').live('ajax:aborted:file', function(event, elements){
  1395. * // Implement own remote file-transfer handler here for non-blank file inputs passed in `elements`.
  1396. * // Returning false in this handler tells rails.js to disallow standard form submission
  1397. * return false;
  1398. * });
  1399. *
  1400. * The `ajax:aborted:file` event is fired when a file-type input is detected with a non-blank value.
  1401. *
  1402. * Third-party tools can use this hook to detect when an AJAX file upload is attempted, and then use
  1403. * techniques like the iframe method to upload the file instead.
  1404. *
  1405. * Required fields in rails.js
  1406. * ===========================
  1407. *
  1408. * If any blank required inputs (required="required") are detected in the remote form, the whole form submission
  1409. * is canceled. Note that this is unlike file inputs, which still allow standard (non-AJAX) form submission.
  1410. *
  1411. * The `ajax:aborted:required` event allows you to bind your own handler to inform the user of blank required inputs.
  1412. *
  1413. * !! Note that Opera does not fire the form's submit event if there are blank required inputs, so this event may never
  1414. * get fired in Opera. This event is what causes other browsers to exhibit the same submit-aborting behavior.
  1415. *
  1416. * Ex:
  1417. * $('form').live('ajax:aborted:required', function(event, elements){
  1418. * // Returning false in this handler tells rails.js to submit the form anyway.
  1419. * // The blank required inputs are passed to this function in `elements`.
  1420. * return ! confirm("Would you like to submit the form with missing info?");
  1421. * });
  1422. */
  1423. // Shorthand to make it a little easier to call public rails functions from within rails.js
  1424. var rails;
  1425. $.rails = rails = {
  1426. // Link elements bound by jquery-ujs
  1427. linkClickSelector: 'a[data-confirm], a[data-method], a[data-remote], a[data-disable-with]',
  1428. // Select elements bound by jquery-ujs
  1429. inputChangeSelector: 'select[data-remote], input[data-remote], textarea[data-remote]',
  1430. // Form elements bound by jquery-ujs
  1431. formSubmitSelector: 'form',
  1432. // Form input elements bound by jquery-ujs
  1433. formInputClickSelector: 'form input[type=submit], form input[type=image], form button[type=submit], form button:not(button[type])',
  1434. // Form input elements disabled during form submission
  1435. disableSelector: 'input[data-disable-with], button[data-disable-with], textarea[data-disable-with]',
  1436. // Form input elements re-enabled after form submission
  1437. enableSelector: 'input[data-disable-with]:disabled, button[data-disable-with]:disabled, textarea[data-disable-with]:disabled',
  1438. // Form required input elements
  1439. requiredInputSelector: 'input[name][required]:not([disabled]),textarea[name][required]:not([disabled])',
  1440. // Form file input elements
  1441. fileInputSelector: 'input:file',
  1442. // Link onClick disable selector with possible reenable after remote submission
  1443. linkDisableSelector: 'a[data-disable-with]',
  1444. // Make sure that every Ajax request sends the CSRF token
  1445. CSRFProtection: function(xhr) {
  1446. var token = $('meta[name="csrf-token"]').attr('content');
  1447. if (token) xhr.setRequestHeader('X-CSRF-Token', token);
  1448. },
  1449. // Triggers an event on an element and returns false if the event result is false
  1450. fire: function(obj, name, data) {
  1451. var event = $.Event(name);
  1452. obj.trigger(event, data);
  1453. return event.result !== false;
  1454. },
  1455. // Default confirm dialog, may be overridden with custom confirm dialog in $.rails.confirm
  1456. confirm: function(message) {
  1457. return confirm(message);
  1458. },
  1459. // Default ajax function, may be overridden with custom function in $.rails.ajax
  1460. ajax: function(options) {
  1461. return $.ajax(options);
  1462. },
  1463. // Submits "remote" forms and links with ajax
  1464. handleRemote: function(element) {
  1465. var method, url, data,
  1466. crossDomain = element.data('cross-domain') || null,
  1467. dataType = element.data('type') || ($.ajaxSettings && $.ajaxSettings.dataType),
  1468. options;
  1469. if (rails.fire(element, 'ajax:before')) {
  1470. if (element.is('form')) {
  1471. method = element.attr('method');
  1472. url = element.attr('action');
  1473. data = element.serializeArray();
  1474. // memoized value from clicked submit button
  1475. var button = element.data('ujs:submit-button');
  1476. if (button) {
  1477. data.push(button);
  1478. element.data('ujs:submit-button', null);
  1479. }
  1480. } else if (element.is(rails.inputChangeSelector)) {
  1481. method = element.data('method');
  1482. url = element.data('url');
  1483. data = element.serialize();
  1484. if (element.data('params')) data = data + "&" + element.data('params');
  1485. } else {
  1486. method = element.data('method');
  1487. url = element.attr('href');
  1488. data = element.data('params') || null;
  1489. }
  1490. options = {
  1491. type: method || 'GET', data: data, dataType: dataType, crossDomain: crossDomain,
  1492. // stopping the "ajax:beforeSend" event will cancel the ajax request
  1493. beforeSend: function(xhr, settings) {
  1494. if (settings.dataType === undefined) {
  1495. xhr.setRequestHeader('accept', '*/*;q=0.5, ' + settings.accepts.script);
  1496. }
  1497. return rails.fire(element, 'ajax:beforeSend', [xhr, settings]);
  1498. },
  1499. success: function(data, status, xhr) {
  1500. element.trigger('ajax:success', [data, status, xhr]);
  1501. },
  1502. complete: function(xhr, status) {
  1503. element.trigger('ajax:complete', [xhr, status]);
  1504. },
  1505. error: function(xhr, status, error) {
  1506. element.trigger('ajax:error', [xhr, status, error]);
  1507. }
  1508. };
  1509. // Only pass url to `ajax` options if not blank
  1510. if (url) { options.url = url; }
  1511. return rails.ajax(options);
  1512. } else {
  1513. return false;
  1514. }
  1515. },
  1516. // Handles "data-method" on links such as:
  1517. // <a href="/users/5" data-method="delete" rel="nofollow" data-confirm="Are you sure?">Delete</a>
  1518. handleMethod: function(link) {
  1519. var href = link.attr('href'),
  1520. method = link.data('method'),
  1521. target = link.attr('target'),
  1522. csrf_token = $('meta[name=csrf-token]').attr('content'),
  1523. csrf_param = $('meta[name=csrf-param]').attr('content'),
  1524. form = $('<form method="post" action="' + href + '"></form>'),
  1525. metadata_input = '<input name="_method" value="' + method + '" type="hidden" />';
  1526. if (csrf_param !== undefined && csrf_token !== undefined) {
  1527. metadata_input += '<input name="' + csrf_param + '" value="' + csrf_token + '" type="hidden" />';
  1528. }
  1529. if (target) { form.attr('target', target); }
  1530. form.hide().append(metadata_input).appendTo('body');
  1531. form.submit();
  1532. },
  1533. /* Disables form elements:
  1534. - Caches element value in 'ujs:enable-with' data store
  1535. - Replaces element text with value of 'data-disable-with' attribute
  1536. - Sets disabled property to true
  1537. */
  1538. disableFormElements: function(form) {
  1539. form.find(rails.disableSelector).each(function() {
  1540. var element = $(this), method = element.is('button') ? 'html' : 'val';
  1541. element.data('ujs:enable-with', element[method]());
  1542. element[method](element.data('disable-with'));
  1543. element.prop('disabled', true);
  1544. });
  1545. },
  1546. /* Re-enables disabled form elements:
  1547. - Replaces element text with cached value from 'ujs:enable-with' data store (created in `disableFormElements`)
  1548. - Sets disabled property to false
  1549. */
  1550. enableFormElements: function(form) {
  1551. form.find(rails.enableSelector).each(function() {
  1552. var element = $(this), method = element.is('button') ? 'html' : 'val';
  1553. if (element.data('ujs:enable-with')) element[method](element.data('ujs:enable-with'));
  1554. element.prop('disabled', false);
  1555. });
  1556. },
  1557. /* For 'data-confirm' attribute:
  1558. - Fires `confirm` event
  1559. - Shows the confirmation dialog
  1560. - Fires the `confirm:complete` event
  1561. Returns `true` if no function stops the chain and user chose yes; `false` otherwise.
  1562. Attaching a handler to the element's `confirm` event that returns a `falsy` value cancels the confirmation dialog.
  1563. Attaching a handler to the element's `confirm:complete` event that returns a `falsy` value makes this function
  1564. return false. The `confirm:complete` event is fired whether or not the user answered true or false to the dialog.
  1565. */
  1566. allowAction: function(element) {
  1567. var message = element.data('confirm'),
  1568. answer = false, callback;
  1569. if (!message) { return true; }
  1570. if (rails.fire(element, 'confirm')) {
  1571. answer = rails.confirm(message);
  1572. callback = rails.fire(element, 'confirm:complete', [answer]);
  1573. }
  1574. return answer && callback;
  1575. },
  1576. // Helper function which checks for blank inputs in a form that match the specified CSS selector
  1577. blankInputs: function(form, specifiedSelector, nonBlank) {
  1578. var inputs = $(), input,
  1579. selector = specifiedSelector || 'input,textarea';
  1580. form.find(selector).each(function() {
  1581. input = $(this);
  1582. // Collect non-blank inputs if nonBlank option is true, otherwise, collect blank inputs
  1583. if (nonBlank ? input.val() : !input.val()) {
  1584. inputs = inputs.add(input);
  1585. }
  1586. });
  1587. return inputs.length ? inputs : false;
  1588. },
  1589. // Helper function which checks for non-blank inputs in a form that match the specified CSS selector
  1590. nonBlankInputs: function(form, specifiedSelector) {
  1591. return rails.blankInputs(form, specifiedSelector, true); // true specifies nonBlank
  1592. },
  1593. // Helper function, needed to provide consistent behavior in IE
  1594. stopEverything: function(e) {
  1595. $(e.target).trigger('ujs:everythingStopped');
  1596. e.stopImmediatePropagation();
  1597. return false;
  1598. },
  1599. // find all the submit events directly bound to the form and
  1600. // manually invoke them. If anyone returns false then stop the loop
  1601. callFormSubmitBindings: function(form, event) {
  1602. var events = form.data('events'), continuePropagation = true;
  1603. if (events !== undefined && events['submit'] !== undefined) {
  1604. $.each(events['submit'], function(i, obj){
  1605. if (typeof obj.handler === 'function') return continuePropagation = obj.handler(event);
  1606. });
  1607. }
  1608. return continuePropagation;
  1609. },
  1610. // replace element's html with the 'data-disable-with' after storing original html
  1611. // and prevent clicking on it
  1612. disableElement: function(element) {
  1613. element.data('ujs:enable-with', element.html()); // store enabled state
  1614. element.html(element.data('disable-with')); // set to disabled state
  1615. element.bind('click.railsDisable', function(e) { // prevent further clicking
  1616. return rails.stopEverything(e)
  1617. });
  1618. },
  1619. // restore element to its original state which was disabled by 'disableElement' above
  1620. enableElement: function(element) {
  1621. if (element.data('ujs:enable-with') !== undefined) {
  1622. element.html(element.data('ujs:enable-with')); // set to old enabled state
  1623. // this should be element.removeData('ujs:enable-with')
  1624. // but, there is currently a bug in jquery which makes hyphenated data attributes not get removed
  1625. element.data('ujs:enable-with', false); // clean up cache
  1626. }
  1627. element.unbind('click.railsDisable'); // enable element
  1628. }
  1629. };
  1630. $.ajaxPrefilter(function(options, originalOptions, xhr){ if ( !options.crossDomain ) { rails.CSRFProtection(xhr); }});
  1631. $(document).delegate(rails.linkDisableSelector, 'ajax:complete', function() {
  1632. rails.enableElement($(this));
  1633. });
  1634. $(document).delegate(rails.linkClickSelector, 'click.rails', function(e) {
  1635. var link = $(this), method = link.data('method'), data = link.data('params');
  1636. if (!rails.allowAction(link)) return rails.stopEverything(e);
  1637. if (link.is(rails.linkDisableSelector)) rails.disableElement(link);
  1638. if (link.data('remote') !== undefined) {
  1639. if ( (e.metaKey || e.ctrlKey) && (!method || method === 'GET') && !data ) { return true; }
  1640. if (rails.handleRemote(link) === false) { rails.enableElement(link); }
  1641. return false;
  1642. } else if (link.data('method')) {
  1643. rails.handleMethod(link);
  1644. return false;
  1645. }
  1646. });
  1647. $(document).delegate(rails.inputChangeSelector, 'change.rails', function(e) {
  1648. var link = $(this);
  1649. if (!rails.allowAction(link)) return rails.stopEverything(e);
  1650. rails.handleRemote(link);
  1651. return false;
  1652. });
  1653. $(document).delegate(rails.formSubmitSelector, 'submit.rails', function(e) {
  1654. var form = $(this),
  1655. remote = form.data('remote') !== undefined,
  1656. blankRequiredInputs = rails.blankInputs(form, rails.requiredInputSelector),
  1657. nonBlankFileInputs = rails.nonBlankInputs(form, rails.fileInputSelector);
  1658. if (!rails.allowAction(form)) return rails.stopEverything(e);
  1659. // skip other logic when required values are missing or file upload is present
  1660. if (blankRequiredInputs && form.attr("novalidate") == undefined && rails.fire(form, 'ajax:aborted:required', [blankRequiredInputs])) {
  1661. return rails.stopEverything(e);
  1662. }
  1663. if (remote) {
  1664. if (nonBlankFileInputs) {
  1665. return rails.fire(form, 'ajax:aborted:file', [nonBlankFileInputs]);
  1666. }
  1667. // If browser does not support submit bubbling, then this live-binding will be called before direct
  1668. // bindings. Therefore, we should directly call any direct bindings before remotely submitting form.
  1669. if (!$.support.submitBubbles && $().jquery < '1.7' && rails.callFormSubmitBindings(form, e) === false) return rails.stopEverything(e);
  1670. rails.handleRemote(form);
  1671. return false;
  1672. } else {
  1673. // slight timeout so that the submit button gets properly serialized
  1674. setTimeout(function(){ rails.disableFormElements(form); }, 13);
  1675. }
  1676. });
  1677. $(document).delegate(rails.formInputClickSelector, 'click.rails', function(event) {
  1678. var button = $(this);
  1679. if (!rails.allowAction(button)) return rails.stopEverything(event);
  1680. // register the pressed submit button
  1681. var name = button.attr('name'),
  1682. data = name ? {name:name, value:button.val()} : null;
  1683. button.closest('form').data('ujs:submit-button', data);
  1684. });
  1685. $(document).delegate(rails.formSubmitSelector, 'ajax:beforeSend.rails', function(event) {
  1686. if (this == event.target) rails.disableFormElements($(this));
  1687. });
  1688. $(document).delegate(rails.formSubmitSelector, 'ajax:complete.rails', function(event) {
  1689. if (this == event.target) rails.enableFormElements($(this));
  1690. });
  1691. })( jQuery );
  1692. $.QueryString = (function(a) {
  1693. if (a == "") return {};
  1694. var b = {};
  1695. for (var i = 0; i < a.length; ++i)
  1696. {
  1697. var p=a[i].split('=');
  1698. if (p.length != 2) continue;
  1699. b[p[0]] = decodeURIComponent(p[1].replace(/\+/g, " "));
  1700. }
  1701. return b;
  1702. })(window.location.search.substr(1).split('&'))
  1703. $(document).ready(function(){
  1704. var selector = "div.gal-item div.gal-inner-holder";
  1705. $(document)
  1706. .on('mouseover', selector, function(e){
  1707. $(this).addClass('hover');
  1708. })
  1709. .on('mouseout', selector, function(e){
  1710. $(this).removeClass('hover');
  1711. })
  1712. .on('click', selector, function(e){
  1713. var url = $(this).parents('div.gal-item').data('url');
  1714. CKEDITOR.tools.callFunction(CKEditorFuncNum, url);
  1715. window.close();
  1716. });
  1717. $(document).on('ajax:complete', "div.gal-item a.gal-del", function(xhr, status){
  1718. $(this).parents('div.gal-item').remove();
  1719. });
  1720. var endlessScroll = $.endlessScroll({
  1721. container: ".fileupload-list",
  1722. entity: ".gal-item",
  1723. scrollPadding: 100
  1724. });
  1725. // Don't listen events first second
  1726. endlessScroll.scrollModule.unbind();
  1727. window.setTimeout(function() { endlessScroll.scrollModule.bind();}, 1000);
  1728. });
  1729. // Collection of all instances on page
  1730. qq.FileUploader.instances = new Object();
  1731. /**
  1732. * Class that creates upload widget with drag-and-drop and file list
  1733. * @inherits qq.FileUploaderBasic
  1734. */
  1735. qq.FileUploaderInput = function(o){
  1736. // call parent constructor
  1737. qq.FileUploaderBasic.apply(this, arguments);
  1738. // additional options
  1739. qq.extend(this._options, {
  1740. element: null,
  1741. // if set, will be used instead of qq-upload-list in template
  1742. listElement: null,
  1743. template_id: '#fileupload_tmpl',
  1744. classes: {
  1745. // used to get elements from templates
  1746. button: 'fileupload-button',
  1747. drop: 'fileupload-drop-area',
  1748. dropActive: 'fileupload-drop-area-active',
  1749. list: 'fileupload-list',
  1750. preview: 'fileupload-preview',
  1751. file: 'fileupload-file',
  1752. spinner: 'fileupload-spinner',
  1753. size: 'fileupload-size',
  1754. cancel: 'fileupload-cancel',
  1755. // added to list item when upload completes
  1756. // used in css to hide progress spinner
  1757. success: 'fileupload-success',
  1758. fail: 'fileupload-fail'
  1759. }
  1760. });
  1761. // overwrite options with user supplied
  1762. qq.extend(this._options, o);
  1763. this._element = document.getElementById(this._options.element);
  1764. this._listElement = this._options.listElement || this._find(this._element, 'list');
  1765. this._classes = this._options.classes;
  1766. this._button = this._createUploadButton(this._find(this._element, 'button'));
  1767. //this._setupDragDrop();
  1768. qq.FileUploader.instances[this._element.id] = this;
  1769. };
  1770. // inherit from Basic Uploader
  1771. qq.extend(qq.FileUploaderInput.prototype, qq.FileUploaderBasic.prototype);
  1772. qq.extend(qq.FileUploaderInput.prototype, {
  1773. /**
  1774. * Gets one of the elements listed in this._options.classes
  1775. **/
  1776. _find: function(parent, type){
  1777. var element = qq.getByClass(parent, this._options.classes[type])[0];
  1778. if (!element){
  1779. alert(type);
  1780. throw new Error('element not found ' + type);
  1781. }
  1782. return element;
  1783. },
  1784. _setupDragDrop: function(){
  1785. var self = this,
  1786. dropArea = this._find(this._element, 'drop');
  1787. var dz = new qq.UploadDropZone({
  1788. element: dropArea,
  1789. onEnter: function(e){
  1790. qq.addClass(dropArea, self._classes.dropActive);
  1791. e.stopPropagation();
  1792. },
  1793. onLeave: function(e){
  1794. e.stopPropagation();
  1795. },
  1796. onLeaveNotDescendants: function(e){
  1797. qq.removeClass(dropArea, self._classes.dropActive);
  1798. },
  1799. onDrop: function(e){
  1800. dropArea.style.display = 'none';
  1801. qq.removeClass(dropArea, self._classes.dropActive);
  1802. self._uploadFileList(e.dataTransfer.files);
  1803. }
  1804. });
  1805. dropArea.style.display = 'none';
  1806. qq.attach(document, 'dragenter', function(e){
  1807. if (!dz._isValidFileDrag(e)) return;
  1808. dropArea.style.display = 'block';
  1809. });
  1810. qq.attach(document, 'dragleave', function(e){
  1811. if (!dz._isValidFileDrag(e)) return;
  1812. var relatedTarget = document.elementFromPoint(e.clientX, e.clientY);
  1813. // only fire when leaving document out
  1814. if ( ! relatedTarget || relatedTarget.nodeName == "HTML"){
  1815. dropArea.style.display = 'none';
  1816. }
  1817. });
  1818. },
  1819. _onSubmit: function(id, fileName){
  1820. qq.FileUploaderBasic.prototype._onSubmit.apply(this, arguments);
  1821. this._addToList(id, fileName);
  1822. },
  1823. _onProgress: function(id, fileName, loaded, total){
  1824. qq.FileUploaderBasic.prototype._onProgress.apply(this, arguments);
  1825. var item = this._getItemByFileId(id);
  1826. var size = this._find(item, 'size');
  1827. var text;
  1828. if (loaded != total){
  1829. text = Math.round(loaded / total * 100) + '% from ' + this._formatSize(total);
  1830. } else {
  1831. text = this._formatSize(total);
  1832. }
  1833. qq.setText(size, text);
  1834. },
  1835. _onComplete: function(id, fileName, result){
  1836. qq.FileUploaderBasic.prototype._onComplete.apply(this, arguments);
  1837. var item = this._getItemByFileId(id);
  1838. var asset = result.asset ? result.asset : result;
  1839. if (asset && asset.id){
  1840. qq.addClass(item, this._classes.success);
  1841. asset.size = this._formatSize(asset.size);
  1842. asset.controller = (asset.type !== undefined && asset.type.toLowerCase() == "ckeditor::picture" ? "pictures" : "attachment_files");
  1843. $(item).replaceWith($(this._options.template_id).tmpl(asset));
  1844. } else {
  1845. qq.addClass(item, this._classes.fail);
  1846. }
  1847. },
  1848. _addToList: function(id, fileName){
  1849. if (this._listElement) {
  1850. if (this._options.multiple === false) {
  1851. $(this._listElement).empty();
  1852. }
  1853. var asset = {
  1854. id: 0,
  1855. filename: this._formatFileName(fileName),
  1856. size: 0,
  1857. format_created_at: '',
  1858. url_content: "#",
  1859. controller: "assets",
  1860. url_thumb: "/assets/ckeditor/filebrowser/images/preloader-3799a3e41d7787a31dac5796ebccc242951da2f2b57eb088326ab3bffe15056a.gif"
  1861. };
  1862. var item = $(this._options.template_id)
  1863. .tmpl(asset)
  1864. .attr('qqfileid', id)
  1865. .prependTo( this._listElement );
  1866. item.find('div.img').addClass('preloader');
  1867. this._bindCancelEvent(item);
  1868. }
  1869. },
  1870. _getItemByFileId: function(id){
  1871. return $(this._listElement).find('div[qqfileid=' + id +']').get(0);
  1872. },
  1873. /**
  1874. * delegate click event for cancel link
  1875. **/
  1876. _bindCancelEvent: function(element){
  1877. var self = this,
  1878. item = $(element);
  1879. item.find('a.' + this._classes.cancel).bind('click', function(e){
  1880. self._handler.cancel( item.attr('qqfileid') );
  1881. item.remove();
  1882. qq.preventDefault(e);
  1883. return false;
  1884. });
  1885. }
  1886. });