populate-me 0.0.33 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (86) hide show
  1. checksums.yaml +5 -5
  2. data/.gitignore +1 -0
  3. data/LICENSE +1 -1
  4. data/README.md +399 -4
  5. data/Rakefile +14 -0
  6. data/example/config.ru +67 -0
  7. data/lib/populate_me.rb +2 -0
  8. data/lib/populate_me/admin.rb +143 -0
  9. data/lib/populate_me/admin/__assets__/css/main.css +174 -0
  10. data/lib/populate_me/admin/__assets__/js/columnav.js +82 -0
  11. data/lib/populate_me/admin/__assets__/js/main.js +251 -0
  12. data/lib/populate_me/admin/__assets__/js/mustache.js +578 -0
  13. data/lib/populate_me/admin/__assets__/js/sortable.js +2 -0
  14. data/lib/populate_me/admin/views/page.erb +163 -0
  15. data/lib/populate_me/api.rb +124 -0
  16. data/lib/populate_me/attachment.rb +182 -0
  17. data/lib/populate_me/document.rb +178 -0
  18. data/lib/populate_me/document_mixins/admin_adapter.rb +131 -0
  19. data/lib/populate_me/document_mixins/callbacks.rb +104 -0
  20. data/lib/populate_me/document_mixins/outcasting.rb +49 -0
  21. data/lib/populate_me/document_mixins/persistence.rb +92 -0
  22. data/lib/populate_me/document_mixins/schema.rb +99 -0
  23. data/lib/populate_me/document_mixins/typecasting.rb +60 -0
  24. data/lib/populate_me/document_mixins/validation.rb +44 -0
  25. data/lib/populate_me/file_system_attachment.rb +40 -0
  26. data/lib/populate_me/grid_fs_attachment.rb +127 -0
  27. data/lib/populate_me/mongo.rb +98 -3
  28. data/lib/populate_me/variation.rb +34 -0
  29. data/lib/populate_me/version.rb +4 -0
  30. data/populate-me.gemspec +20 -12
  31. data/test/helper.rb +37 -0
  32. data/test/test_admin.rb +161 -0
  33. data/test/test_api.rb +246 -0
  34. data/test/test_attachment.rb +155 -0
  35. data/test/test_document.rb +120 -0
  36. data/test/test_document_admin_adapter.rb +43 -0
  37. data/test/test_document_callbacks.rb +107 -0
  38. data/test/test_document_persistence.rb +56 -0
  39. data/test/test_document_typecasting.rb +121 -0
  40. data/test/test_mongo.rb +217 -0
  41. data/test/test_variation.rb +91 -0
  42. data/test/test_version.rb +11 -0
  43. metadata +115 -66
  44. data/lib/populate_me/control.rb +0 -196
  45. data/lib/populate_me/control/_public/css/main.css +0 -207
  46. data/lib/populate_me/control/_public/css/plugin.asmselect.css +0 -63
  47. data/lib/populate_me/control/_public/css/ui-darkness/images/ui-bg_flat_30_cccccc_40x100.png +0 -0
  48. data/lib/populate_me/control/_public/css/ui-darkness/images/ui-bg_flat_50_5c5c5c_40x100.png +0 -0
  49. data/lib/populate_me/control/_public/css/ui-darkness/images/ui-bg_glass_20_555555_1x400.png +0 -0
  50. data/lib/populate_me/control/_public/css/ui-darkness/images/ui-bg_glass_40_0078a3_1x400.png +0 -0
  51. data/lib/populate_me/control/_public/css/ui-darkness/images/ui-bg_glass_40_ffc73d_1x400.png +0 -0
  52. data/lib/populate_me/control/_public/css/ui-darkness/images/ui-bg_gloss-wave_25_333333_500x100.png +0 -0
  53. data/lib/populate_me/control/_public/css/ui-darkness/images/ui-bg_highlight-soft_80_eeeeee_1x100.png +0 -0
  54. data/lib/populate_me/control/_public/css/ui-darkness/images/ui-bg_inset-soft_25_000000_1x100.png +0 -0
  55. data/lib/populate_me/control/_public/css/ui-darkness/images/ui-bg_inset-soft_30_f58400_1x100.png +0 -0
  56. data/lib/populate_me/control/_public/css/ui-darkness/images/ui-icons_222222_256x240.png +0 -0
  57. data/lib/populate_me/control/_public/css/ui-darkness/images/ui-icons_4b8e0b_256x240.png +0 -0
  58. data/lib/populate_me/control/_public/css/ui-darkness/images/ui-icons_a83300_256x240.png +0 -0
  59. data/lib/populate_me/control/_public/css/ui-darkness/images/ui-icons_cccccc_256x240.png +0 -0
  60. data/lib/populate_me/control/_public/css/ui-darkness/images/ui-icons_ffffff_256x240.png +0 -0
  61. data/lib/populate_me/control/_public/css/ui-darkness/jquery-ui-1.8.17.custom.css +0 -430
  62. data/lib/populate_me/control/_public/img/grip.png +0 -0
  63. data/lib/populate_me/control/_public/img/icons-cms-solarized.png +0 -0
  64. data/lib/populate_me/control/_public/img/icons-cms.png +0 -0
  65. data/lib/populate_me/control/_public/img/placeholder.png +0 -0
  66. data/lib/populate_me/control/_public/img/placeholder.stash_thumb.gif +0 -0
  67. data/lib/populate_me/control/_public/img/placeholder.stash_thumb.png +0 -0
  68. data/lib/populate_me/control/_public/img/small-loader.gif +0 -0
  69. data/lib/populate_me/control/_public/js/addon.timepicker.js +0 -20
  70. data/lib/populate_me/control/_public/js/jquery-ui.js +0 -114
  71. data/lib/populate_me/control/_public/js/jquery.js +0 -167
  72. data/lib/populate_me/control/_public/js/jquery.mustache.js +0 -559
  73. data/lib/populate_me/control/_public/js/main.js +0 -144
  74. data/lib/populate_me/control/_public/js/plugin.asmselect.js +0 -407
  75. data/lib/populate_me/control/_public/js/plugin.form.js +0 -11
  76. data/lib/populate_me/control/_public/js/plugin.quicksearch.js +0 -1
  77. data/lib/populate_me/control/_public/js/plugin.underwood.js +0 -4
  78. data/lib/populate_me/control/views/populate_me_layout.erb +0 -75
  79. data/lib/populate_me/ext.rb +0 -16
  80. data/lib/populate_me/mongo/backend_api_plug.rb +0 -78
  81. data/lib/populate_me/mongo/crushyform.rb +0 -243
  82. data/lib/populate_me/mongo/mutation.rb +0 -324
  83. data/lib/populate_me/mongo/plug.rb +0 -134
  84. data/lib/populate_me/mongo/stash.rb +0 -171
  85. data/test/spec_ext.rb +0 -29
  86. data/test/spec_mongo_mutation.rb +0 -279
@@ -1,11 +0,0 @@
1
- /*!
2
- * jQuery Form Plugin
3
- * version: 2.83 (11-JUL-2011)
4
- * @requires jQuery v1.3.2 or later
5
- *
6
- * Examples and documentation at: http://malsup.com/jquery/form/
7
- * Dual licensed under the MIT and GPL licenses:
8
- * http://www.opensource.org/licenses/mit-license.php
9
- * http://www.gnu.org/licenses/gpl.html
10
- */
11
- ;(function($){$.fn.ajaxSubmit=function(w){if(!this.length){log('ajaxSubmit: skipping submit process - no element selected');return this}var x,action,url,$form=this;if(typeof w=='function'){w={success:w}}x=this.attr('method');action=this.attr('action');url=(typeof action==='string')?$.trim(action):'';url=url||window.location.href||'';if(url){url=(url.match(/^([^#]+)/)||[])[1]}w=$.extend(true,{url:url,success:$.ajaxSettings.success,type:x||'GET',iframeSrc:/^https/i.test(window.location.href||'')?'javascript:false':'about:blank'},w);var y={};this.trigger('form-pre-serialize',[this,w,y]);if(y.veto){log('ajaxSubmit: submit vetoed via form-pre-serialize trigger');return this}if(w.beforeSerialize&&w.beforeSerialize(this,w)===false){log('ajaxSubmit: submit aborted via beforeSerialize callback');return this}var n,v,a=this.formToArray(w.semantic);if(w.data){w.extraData=w.data;for(n in w.data){if(w.data[n]instanceof Array){for(var k in w.data[n]){a.push({name:n,value:w.data[n][k]})}}else{v=w.data[n];v=$.isFunction(v)?v():v;a.push({name:n,value:v})}}}if(w.beforeSubmit&&w.beforeSubmit(a,this,w)===false){log('ajaxSubmit: submit aborted via beforeSubmit callback');return this}this.trigger('form-submit-validate',[a,this,w,y]);if(y.veto){log('ajaxSubmit: submit vetoed via form-submit-validate trigger');return this}var q=$.param(a);if(w.type.toUpperCase()=='GET'){w.url+=(w.url.indexOf('?')>=0?'&':'?')+q;w.data=null}else{w.data=q}var z=[];if(w.resetForm){z.push(function(){$form.resetForm()})}if(w.clearForm){z.push(function(){$form.clearForm()})}if(!w.dataType&&w.target){var A=w.success||function(){};z.push(function(a){var b=w.replaceTarget?'replaceWith':'html';$(w.target)[b](a).each(A,arguments)})}else if(w.success){z.push(w.success)}w.success=function(a,b,c){var d=w.context||w;for(var i=0,max=z.length;i<max;i++){z[i].apply(d,[a,b,c||$form,$form])}};var B=$('input:file',this).length>0;var C='multipart/form-data';var D=($form.attr('enctype')==C||$form.attr('encoding')==C);if(w.iframe!==false&&(B||w.iframe||D)){if(w.closeKeepAlive){$.get(w.closeKeepAlive,function(){fileUpload(a)})}else{fileUpload(a)}}else{if($.browser.msie&&x=='get'){var E=$form[0].getAttribute('method');if(typeof E==='string')w.type=E}$.ajax(w)}this.trigger('form-submit-notify',[this,w]);return this;function fileUpload(a){var l=$form[0],el,i,s,g,id,$io,io,xhr,sub,n,timedOut,timeoutHandle;var m=!!$.fn.prop;if(a){for(i=0;i<a.length;i++){el=$(l[a[i].name]);el[m?'prop':'attr']('disabled',false)}}if($(':input[name=submit],:input[id=submit]',l).length){alert('Error: Form elements must not have name or id of "submit".');return}s=$.extend(true,{},$.ajaxSettings,w);s.context=s.context||s;id='jqFormIO'+(new Date().getTime());if(s.iframeTarget){$io=$(s.iframeTarget);n=$io.attr('name');if(n==null)$io.attr('name',id);else id=n}else{$io=$('<iframe name="'+id+'" src="'+s.iframeSrc+'" />');$io.css({position:'absolute',top:'-1000px',left:'-1000px'})}io=$io[0];xhr={aborted:0,responseText:null,responseXML:null,status:0,statusText:'n/a',getAllResponseHeaders:function(){},getResponseHeader:function(){},setRequestHeader:function(){},abort:function(a){var e=(a==='timeout'?'timeout':'aborted');log('aborting upload... '+e);this.aborted=1;$io.attr('src',s.iframeSrc);xhr.error=e;s.error&&s.error.call(s.context,xhr,e,a);g&&$.event.trigger("ajaxError",[xhr,s,e]);s.complete&&s.complete.call(s.context,xhr,e)}};g=s.global;if(g&&!$.active++){$.event.trigger("ajaxStart")}if(g){$.event.trigger("ajaxSend",[xhr,s])}if(s.beforeSend&&s.beforeSend.call(s.context,xhr,s)===false){if(s.global){$.active--}return}if(xhr.aborted){return}sub=l.clk;if(sub){n=sub.name;if(n&&!sub.disabled){s.extraData=s.extraData||{};s.extraData[n]=sub.value;if(sub.type=="image"){s.extraData[n+'.x']=l.clk_x;s.extraData[n+'.y']=l.clk_y}}}var o=1;var p=2;function getDoc(a){var b=a.contentWindow?a.contentWindow.document:a.contentDocument?a.contentDocument:a.document;return b}function doSubmit(){var t=$form.attr('target'),a=$form.attr('action');l.setAttribute('target',id);if(!x){l.setAttribute('method','POST')}if(a!=s.url){l.setAttribute('action',s.url)}if(!s.skipEncodingOverride&&(!x||/post/i.test(x))){$form.attr({encoding:'multipart/form-data',enctype:'multipart/form-data'})}if(s.timeout){timeoutHandle=setTimeout(function(){timedOut=true;cb(o)},s.timeout)}function checkState(){try{var a=getDoc(io).readyState;log('state = '+a);if(a.toLowerCase()=='uninitialized')setTimeout(checkState,50)}catch(e){log('Server abort: ',e,' (',e.name,')');cb(p);timeoutHandle&&clearTimeout(timeoutHandle);timeoutHandle=undefined}}var b=[];try{if(s.extraData){for(var n in s.extraData){b.push($('<input type="hidden" name="'+n+'" />').attr('value',s.extraData[n]).appendTo(l)[0])}}if(!s.iframeTarget){$io.appendTo('body');io.attachEvent?io.attachEvent('onload',cb):io.addEventListener('load',cb,false)}setTimeout(checkState,15);l.submit()}finally{l.setAttribute('action',a);if(t){l.setAttribute('target',t)}else{$form.removeAttr('target')}$(b).remove()}}if(s.forceSync){doSubmit()}else{setTimeout(doSubmit,10)}var q,doc,domCheckCount=50,callbackProcessed;function cb(e){if(xhr.aborted||callbackProcessed){return}try{doc=getDoc(io)}catch(ex){log('cannot access response document: ',ex);e=p}if(e===o&&xhr){xhr.abort('timeout');return}else if(e==p&&xhr){xhr.abort('server abort');return}if(!doc||doc.location.href==s.iframeSrc){if(!timedOut)return}io.detachEvent?io.detachEvent('onload',cb):io.removeEventListener('load',cb,false);var c='success',errMsg;try{if(timedOut){throw'timeout';}var d=s.dataType=='xml'||doc.XMLDocument||$.isXMLDoc(doc);log('isXml='+d);if(!d&&window.opera&&(doc.body==null||doc.body.innerHTML=='')){if(--domCheckCount){log('requeing onLoad callback, DOM not available');setTimeout(cb,250);return}}var f=doc.body?doc.body:doc.documentElement;xhr.responseText=f?f.innerHTML:null;xhr.responseXML=doc.XMLDocument?doc.XMLDocument:doc;if(d)s.dataType='xml';xhr.getResponseHeader=function(a){var b={'content-type':s.dataType};return b[a]};if(f){xhr.status=Number(f.getAttribute('status'))||xhr.status;xhr.statusText=f.getAttribute('statusText')||xhr.statusText}var h=s.dataType||'';var i=/(json|script|text)/.test(h.toLowerCase());if(i||s.textarea){var j=doc.getElementsByTagName('textarea')[0];if(j){xhr.responseText=j.value;xhr.status=Number(j.getAttribute('status'))||xhr.status;xhr.statusText=j.getAttribute('statusText')||xhr.statusText}else if(i){var k=doc.getElementsByTagName('pre')[0];var b=doc.getElementsByTagName('body')[0];if(k){xhr.responseText=k.textContent?k.textContent:k.innerHTML}else if(b){xhr.responseText=b.innerHTML}}}else if(s.dataType=='xml'&&!xhr.responseXML&&xhr.responseText!=null){xhr.responseXML=r(xhr.responseText)}try{q=v(xhr,s.dataType,s)}catch(e){c='parsererror';xhr.error=errMsg=(e||c)}}catch(e){log('error caught: ',e);c='error';xhr.error=errMsg=(e||c)}if(xhr.aborted){log('upload aborted');c=null}if(xhr.status){c=(xhr.status>=200&&xhr.status<300||xhr.status===304)?'success':'error'}if(c==='success'){s.success&&s.success.call(s.context,q,'success',xhr);g&&$.event.trigger("ajaxSuccess",[xhr,s])}else if(c){if(errMsg==undefined)errMsg=xhr.statusText;s.error&&s.error.call(s.context,xhr,c,errMsg);g&&$.event.trigger("ajaxError",[xhr,s,errMsg])}g&&$.event.trigger("ajaxComplete",[xhr,s]);if(g&&!--$.active){$.event.trigger("ajaxStop")}s.complete&&s.complete.call(s.context,xhr,c);callbackProcessed=true;if(s.timeout)clearTimeout(timeoutHandle);setTimeout(function(){if(!s.iframeTarget)$io.remove();xhr.responseXML=null},100)}var r=$.parseXML||function(s,a){if(window.ActiveXObject){a=new ActiveXObject('Microsoft.XMLDOM');a.async='false';a.loadXML(s)}else{a=(new DOMParser()).parseFromString(s,'text/xml')}return(a&&a.documentElement&&a.documentElement.nodeName!='parsererror')?a:null};var u=$.parseJSON||function(s){return window['eval']('('+s+')')};var v=function(a,b,s){var c=a.getResponseHeader('content-type')||'',xml=b==='xml'||!b&&c.indexOf('xml')>=0,q=xml?a.responseXML:a.responseText;if(xml&&q.documentElement.nodeName==='parsererror'){$.error&&$.error('parsererror')}if(s&&s.dataFilter){q=s.dataFilter(q,b)}if(typeof q==='string'){if(b==='json'||!b&&c.indexOf('json')>=0){q=u(q)}else if(b==="script"||!b&&c.indexOf("javascript")>=0){$.globalEval(q)}}return q}}};$.fn.ajaxForm=function(f){if(this.length===0){var o={s:this.selector,c:this.context};if(!$.isReady&&o.s){log('DOM not ready, queuing ajaxForm');$(function(){$(o.s,o.c).ajaxForm(f)});return this}log('terminating; zero elements found by selector'+($.isReady?'':' (DOM not ready)'));return this}return this.ajaxFormUnbind().bind('submit.form-plugin',function(e){if(!e.isDefaultPrevented()){e.preventDefault();$(this).ajaxSubmit(f)}}).bind('click.form-plugin',function(e){var a=e.target;var b=$(a);if(!(b.is(":submit,input:image"))){var t=b.closest(':submit');if(t.length==0){return}a=t[0]}var c=this;c.clk=a;if(a.type=='image'){if(e.offsetX!=undefined){c.clk_x=e.offsetX;c.clk_y=e.offsetY}else if(typeof $.fn.offset=='function'){var d=b.offset();c.clk_x=e.pageX-d.left;c.clk_y=e.pageY-d.top}else{c.clk_x=e.pageX-a.offsetLeft;c.clk_y=e.pageY-a.offsetTop}}setTimeout(function(){c.clk=c.clk_x=c.clk_y=null},100)})};$.fn.ajaxFormUnbind=function(){return this.unbind('submit.form-plugin click.form-plugin')};$.fn.formToArray=function(b){var a=[];if(this.length===0){return a}var c=this[0];var d=b?c.getElementsByTagName('*'):c.elements;if(!d){return a}var i,j,n,v,el,max,jmax;for(i=0,max=d.length;i<max;i++){el=d[i];n=el.name;if(!n){continue}if(b&&c.clk&&el.type=="image"){if(!el.disabled&&c.clk==el){a.push({name:n,value:$(el).val()});a.push({name:n+'.x',value:c.clk_x},{name:n+'.y',value:c.clk_y})}continue}v=$.fieldValue(el,true);if(v&&v.constructor==Array){for(j=0,jmax=v.length;j<jmax;j++){a.push({name:n,value:v[j]})}}else if(v!==null&&typeof v!='undefined'){a.push({name:n,value:v})}}if(!b&&c.clk){var e=$(c.clk),input=e[0];n=input.name;if(n&&!input.disabled&&input.type=='image'){a.push({name:n,value:e.val()});a.push({name:n+'.x',value:c.clk_x},{name:n+'.y',value:c.clk_y})}}return a};$.fn.formSerialize=function(a){return $.param(this.formToArray(a))};$.fn.fieldSerialize=function(b){var a=[];this.each(function(){var n=this.name;if(!n){return}var v=$.fieldValue(this,b);if(v&&v.constructor==Array){for(var i=0,max=v.length;i<max;i++){a.push({name:n,value:v[i]})}}else if(v!==null&&typeof v!='undefined'){a.push({name:this.name,value:v})}});return $.param(a)};$.fn.fieldValue=function(a){for(var b=[],i=0,max=this.length;i<max;i++){var c=this[i];var v=$.fieldValue(c,a);if(v===null||typeof v=='undefined'||(v.constructor==Array&&!v.length)){continue}v.constructor==Array?$.merge(b,v):b.push(v)}return b};$.fieldValue=function(b,c){var n=b.name,t=b.type,tag=b.tagName.toLowerCase();if(c===undefined){c=true}if(c&&(!n||b.disabled||t=='reset'||t=='button'||(t=='checkbox'||t=='radio')&&!b.checked||(t=='submit'||t=='image')&&b.form&&b.form.clk!=b||tag=='select'&&b.selectedIndex==-1)){return null}if(tag=='select'){var d=b.selectedIndex;if(d<0){return null}var a=[],ops=b.options;var e=(t=='select-one');var f=(e?d+1:ops.length);for(var i=(e?d:0);i<f;i++){var g=ops[i];if(g.selected){var v=g.value;if(!v){v=(g.attributes&&g.attributes['value']&&!(g.attributes['value'].specified))?g.text:g.value}if(e){return v}a.push(v)}}return a}return $(b).val()};$.fn.clearForm=function(){return this.each(function(){$('input,select,textarea',this).clearFields()})};$.fn.clearFields=$.fn.clearInputs=function(){var a=/^(?:color|date|datetime|email|month|number|password|range|search|tel|text|time|url|week)$/i;return this.each(function(){var t=this.type,tag=this.tagName.toLowerCase();if(a.test(t)||tag=='textarea'){this.value=''}else if(t=='checkbox'||t=='radio'){this.checked=false}else if(tag=='select'){this.selectedIndex=-1}})};$.fn.resetForm=function(){return this.each(function(){if(typeof this.reset=='function'||(typeof this.reset=='object'&&!this.reset.nodeType)){this.reset()}})};$.fn.enable=function(b){if(b===undefined){b=true}return this.each(function(){this.disabled=!b})};$.fn.selected=function(b){if(b===undefined){b=true}return this.each(function(){var t=this.type;if(t=='checkbox'||t=='radio'){this.checked=b}else if(this.tagName.toLowerCase()=='option'){var a=$(this).parent('select');if(b&&a[0]&&a[0].type=='select-one'){a.find('option').selected(false)}this.selected=b}})};function log(){var a='[jquery.form] '+Array.prototype.join.call(arguments,'');if(window.console&&window.console.log){window.console.log(a)}else if(window.opera&&window.opera.postError){window.opera.postError(a)}}})(jQuery);
@@ -1 +0,0 @@
1
- (function($,h,j,k){$.fn.quicksearch=function(d,f){var g,cache,rowcache,jq_results,val='',e=this,options=$.extend({delay:100,selector:null,stripeRows:null,loader:null,noResults:'',bind:'keyup',onBefore:function(){return},onAfter:function(){return},show:function(){this.style.display=""},hide:function(){this.style.display="none"},prepareQuery:function(a){return a.toLowerCase().split(' ')},testQuery:function(a,b,c){for(var i=0;i<a.length;i+=1){if(b.indexOf(a[i])===-1){return false}}return true}},f);this.go=function(){var i=0,noresults=true,query=options.prepareQuery(val),val_empty=(val.replace(' ','').length===0);for(var i=0,len=rowcache.length;i<len;i++){if(val_empty||options.testQuery(query,cache[i],rowcache[i])){options.show.apply(rowcache[i]);noresults=false}else{options.hide.apply(rowcache[i])}}if(noresults){this.results(false)}else{this.results(true);this.stripe()}this.loader(false);options.onAfter();return this};this.stripe=function(){if(typeof options.stripeRows==="object"&&options.stripeRows!==null){var a=options.stripeRows.join(' ');var b=options.stripeRows.length;jq_results.not(':hidden').each(function(i){$(this).removeClass(a).addClass(options.stripeRows[i%b])})}return this};this.strip_html=function(a){var b=a.replace(new RegExp('<[^<]+\>','g'),"");b=$.trim(b.toLowerCase());return b};this.results=function(a){if(typeof options.noResults==="string"&&options.noResults!==""){if(a){$(options.noResults).hide()}else{$(options.noResults).show()}}return this};this.loader=function(a){if(typeof options.loader==="string"&&options.loader!==""){(a)?$(options.loader).show():$(options.loader).hide()}return this};this.cache=function(){jq_results=$(d);if(typeof options.noResults==="string"&&options.noResults!==""){jq_results=jq_results.not(options.noResults)}var t=(typeof options.selector==="string")?jq_results.find(options.selector):$(d).not(options.noResults);cache=t.map(function(){return e.strip_html(this.innerHTML)});rowcache=jq_results.map(function(){return this});return this.go()};this.trigger=function(){this.loader(true);options.onBefore();h.clearTimeout(g);g=h.setTimeout(function(){e.go()},options.delay);return this};this.cache();this.results(true);this.stripe();this.loader(false);return this.each(function(){$(this).bind(options.bind,function(){val=$(this).val();e.trigger()})})}}(jQuery,this,document));
@@ -1,4 +0,0 @@
1
- // Copyright (c) 2010-11 Mickael Riga - See MIT_LICENCE for details
2
- // Version 0.2.2
3
- ;(function($){$.fn.underwood=function(l){var m={toolbar:"title paragraph bold italic link mailto unlink source",title_block:'<h3>',paragraph_block:'<p>',sanitize:true,css_href:null};var n=$.extend({},m,l);if(document.designMode||document.contentEditable){return this.each(function(){$(this).parents('form').unbind('submit.underwood').bind('submit.underwood',function(){$(this).find('iframe.underwood_iframe').each(function(){disable_design_mode(this,true)})});enable_design_mode($(this))})}function enable_design_mode(a){var b=document.createElement("iframe");b.frameBorder=0;b.frameMargin=0;b.framePadding=0;b.height=200;b.className="underwood_iframe";if(a.attr('name'))b.rel=a.attr('name');a.after(b);var c;if(n.css_href){c="<link rel='stylesheet' href='"+n.css_href+"' type='text/css' media='screen' charset='utf-8' />"}else{c=" <style type='text/css'> .underwood_frame_body {font-family:sans-serif;font-size:12px;margin:0;padding:10px;} .underwood_frame_body p {border:1px #DDD solid;padding:2px;} </style>"}var d=a.val();if($.trim(d)=='')d='<br>';var e="<html><head>"+c+"</head><body class='underwood_frame_body'>"+d+"</body></html>";try_enable_design_mode(b,e,function(){$(b).prevAll('.underwood_toolbar').remove().end().before(toolbar(b));a.remove()})}function try_enable_design_mode(a,b,c){try{a.contentWindow.document.open();a.contentWindow.document.write(b);a.contentWindow.document.close()}catch(error){console.log(error)}if(document.contentEditable){a.contentWindow.document.designMode="On";c();return true}else if(document.designMode!=null){try{a.contentWindow.document.designMode="on";c();return true}catch(error){console.log(error)}}setTimeout(function(){try_enable_design_mode(a,b,c)},250);return false}function toolbar(e){var f=$("<div class='underwood_toolbar'></div>");var g=n.toolbar.split(' ');for(var i in g){var h=g[i];var j=h.charAt(0).toUpperCase()+h.substr(1).toLowerCase();var k="<a href='#' class='underwood_btn underwood_btn_"+h+"' title='"+j+"'>"+j+"</a>";f.append($(k))}$('.underwood_btn_title',f).click(function(){exec_command(e,"formatblock",n.title_block);return false});$('.underwood_btn_paragraph',f).click(function(){exec_command(e,"formatblock",n.paragraph_block);return false});$('.underwood_btn_bold',f).click(function(){exec_command(e,'bold');return false});$('.underwood_btn_italic',f).click(function(){exec_command(e,'italic');return false});$('.underwood_btn_link',f).click(function(){var a=prompt("URL:","http://");var b=confirm('Open in a new window? (press cancel to open in the same window)')?'_blank':'_self';if(a){exec_command(e,'CreateLink',a);var c=e.contentWindow;var d=c.getSelection();d.focusNode.parentNode.target=b}return false});$('.underwood_btn_mailto',f).click(function(){var a='mailto:'+prompt("Email address:");if(a)exec_command(e,'CreateLink',a);return false});$('.underwood_btn_unlink',f).click(function(){exec_command(e,'unlink');return false});$('.underwood_btn_image',f).click(function(){var a=prompt("Image URL:");if(a)exec_command(e,'InsertImage',a);return false});$('.underwood_btn_source',f).click(function(){var a=disable_design_mode(e);var b=$("<a href='#' class='underwood_btn underwood_btn_back'>Back to Rich Text Editor</a>");f.empty().append(b);b.click(function(){enable_design_mode(a);return false}).hover(function(){$(this).css('opacity',0.6)},function(){$(this).css('opacity',1)});return false});$('.underwood_btn',f).hover(function(){$(this).css('opacity',0.6)},function(){$(this).css('opacity',1)});return f}function exec_command(a,b,c){a.contentWindow.focus();try{a.contentWindow.document.execCommand(b,false,c)}catch(e){console.log(e)}a.contentWindow.focus()}function sanitize_this(s){s=n.sanitize?s.replace(/(<\/?font[^>]*>|style=.[^'"]*['"])/g,''):s;return(s.match(/(>|^)[^<]+(<|$)/)||s.match(/<(object|iframe|img)/))?s:''};function disable_design_mode(a,b){var c=a.contentWindow.document.getElementsByTagName("body")[0].innerHTML;if(b==true)var d=$('<input type="hidden" />');else var d=$('<textarea cols="40" rows="10"></textarea>');d.val(sanitize_this(c));t=d.get(0);t.className="underwood_textarea_copy";if(a.rel)t.name=a.rel;$(a).before(d);if(b!=true)$(a).remove();return d}function create_targeted_link(a,b,c){selected=a.contentWindow.document.getSelection().getRangeAt(0);var d=a.contentWindow.document.createElement('a');d.href=b;d.target=c;if(selected.toString()==''){d.innerHTML=b;selected.insertNode(d)}else{selected.surroundContents(d)}}}})(jQuery);
4
-
@@ -1,75 +0,0 @@
1
- <%# coding: UTF-8 %>
2
- <!DOCTYPE html>
3
- <html lang="en">
4
- <head>
5
- <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
6
- <link rel="stylesheet" href="<%= config[:path] %>/_public/css/plugin.asmselect.css" type="text/css" media="screen" charset="utf-8" />
7
- <link rel="stylesheet" href="<%= config[:path] %>/_public/css/ui-darkness/jquery-ui-1.8.17.custom.css" type="text/css" media="screen" charset="utf-8" />
8
- <link rel="stylesheet" href="<%= config[:path] %>/_public/css/main.css" type="text/css" media="screen" charset="utf-8" />
9
- <title><%= config[:client_name] %> - Admin</title>
10
- <script id='template-menu' type='text/html'>
11
- <ul class='menu-list'>
12
- {{#items}}
13
- <li><a href='{{href}}' class='pushstack'>{{title}}</a></li>
14
- {{/items}}
15
- </ul>
16
- </script>
17
- <script id='template-nut-tree' type='text/html'>
18
- {{#command_plus}}
19
- <a href='<%= config[:path] %>/form/{{class_name}}{{query_string}}' class='btn plus-btn extra-plus pushstack' title='Create'></a>
20
- {{/command_plus}}
21
- <ul class='nut-tree {{#sortable}}sortable{{/sortable}}'>
22
- {{#items}}{{>nutshell}}{{/items}}
23
- </ul>
24
- </script>
25
- <script id='template-nutshell' type='text/html'>
26
- <li class='nutshell nutshell-{{class_name}}' id='{{class_name}}-{{id}}'>
27
- <div class='nutshell-core'>
28
- <div class='nutshell-bar {{#sortable}}sortable-handle{{/sortable}}'>
29
-
30
- <a href='<%= config[:path] %>/{{class_name}}/{{id}}' class='btn cross-btn nutshell-delete float-right' title='Delete'></a>
31
- </div>
32
- <a href='<%= config[:path] %>/form/{{class_name}}/{{id}}' class='nutshell-edit pushstack' title='Edit'>
33
- {{#thumb}}<img src='{{thumb}}' onerror="this.style.display='none'" alt='Thumb' />{{/thumb}}
34
- &nbsp;<span class='nutshell-title'>{{title}}</span>
35
- </a>
36
- </div>
37
- <ul class='nutshell-children'>
38
- {{#children}}
39
- <li><a href='<%= config[:path] %>/list/{{children_class_name}}?filter[{{foreign_key_name}}]={{id}}' class='btn further-btn further-bar pushstack'>{{title}} {{count}}</a></li>
40
- {{/children}}
41
- </ul>
42
- </li>
43
- </script>
44
- </head>
45
- <body>
46
- <div id='trunk'>
47
- <h1 id='page-title' class='shadowed'><%= config[:page_title] %></h1>
48
- <div id='header' class='shadowed'>
49
- <a href='#' class='btn back-btn float-left popstack' title='Back'></a>
50
- <a href='<%= config[:path] %>' class='btn home-btn float-right' title='Home'></a>
51
- <div id='search-wrap'><input type='text' id='search' placeholder='Search' /></div>
52
- </div>
53
- <noscript><div>In order to have this content management system working, you need to have javascript enabled.</div></noscript>
54
- <div id='content'></div>
55
- <div id='footer'>
56
- &copy; Mickaël Riga<br>
57
- <a href='https://github.com/mig-hub/populate-me' target='_blank'>Populate Me</a> is an open source project
58
- </div>
59
- </div>
60
- <script type="text/javascript" charset="utf-8">
61
- var admin_path = "<%= config[:path] %>";
62
- </script>
63
- <!-- // <script src='http://code.jquery.com/jquery-1.6.2.min.js' type='text/javascript'></script> -->
64
- <script src='<%= config[:path] %>/_public/js/jquery.js' type='text/javascript'></script>
65
- <script src='<%= config[:path] %>/_public/js/jquery.mustache.js' type='text/javascript'></script>
66
- <script src='<%= config[:path] %>/_public/js/jquery-ui.js' type='text/javascript'></script>
67
- <script src='<%= config[:path] %>/_public/js/addon.timepicker.js' type='text/javascript'></script>
68
- <script src='<%= config[:path] %>/_public/js/plugin.form.js' type='text/javascript'></script>
69
- <script src='<%= config[:path] %>/_public/js/plugin.underwood.js' type='text/javascript'></script>
70
- <script src='<%= config[:path] %>/_public/js/plugin.asmselect.js' type='text/javascript'></script>
71
- <script src='<%= config[:path] %>/_public/js/plugin.quicksearch.js' type='text/javascript'></script>
72
- <script src='<%= config[:path] %>/_public/js/main.js' type='text/javascript'></script>
73
- </body>
74
- </html>
75
-
@@ -1,16 +0,0 @@
1
- # encoding: utf-8
2
-
3
- # Extends standard classes
4
-
5
- Integer.class_eval do
6
- def to_price_string
7
- ("%.2f" % (self/100.0)).sub(/\.00/, '').reverse.gsub(/(\d{3})(?=\d)/, '\\1,').reverse
8
- end
9
- end
10
-
11
- String.class_eval do
12
- def to_price_integer
13
- ("%.2f" % self.gsub(/[^\d\.\-]/, '')).gsub(/\./,'').to_i
14
- end
15
- end
16
-
@@ -1,78 +0,0 @@
1
- # encoding: utf-8
2
- module PopulateMe
3
- module Mongo
4
- module BackendApiPlug
5
-
6
- # This module adds a layer between the backend API and the models.
7
- # It is legacy code and could probably be removed,
8
- # but in a way it makes it easier to plug something else than MongoDB.
9
- # I'll keep it and see.
10
-
11
- def self.included(base)
12
- base.extend(ClassMethods)
13
- end
14
-
15
- module ClassMethods
16
- def backend_get(id); id=='unique' ? find_one : get(id); end
17
- def backend_post(doc=nil); inst = new(doc); inst.is_new = true; inst; end
18
- end
19
-
20
- # Instance Methods
21
-
22
- def backend_delete; delete; end
23
- def backend_put(fields); update_doc(fields); end
24
- def backend_values; @doc; end
25
- def backend_save?; !save.nil?; end
26
- def backend_form(url, cols=nil, opts={})
27
- cols ||= default_backend_columns
28
- if block_given?
29
- fields_list = ''
30
- yield(fields_list)
31
- else
32
- fields_list = respond_to?(:crushyform) ? crushyform(cols) : backend_fields(cols)
33
- end
34
- o = "<form action='#{url}' method='POST' #{"enctype='multipart/form-data'" if fields_list.match(/type='file'/)} class='backend-form'>\n"
35
- #o << backend_form_title unless block_given?
36
- o << fields_list
37
- opts[:method] = 'PUT' if (opts[:method].nil? && !self.new?)
38
- o << "<input type='hidden' name='_method' value='#{opts[:method]}' />\n" unless opts[:method].nil?
39
- o << "<input type='hidden' name='_destination' value='#{opts[:destination]}' />\n" unless opts[:destination].nil?
40
- o << "<input type='hidden' name='_submit_text' value='#{opts[:submit_text]}' />\n" unless opts[:submit_text].nil?
41
- o << "<input type='hidden' name='_no_wrap' value='#{opts[:no_wrap]}' />\n" unless opts[:no_wrap].nil?
42
- cols.each do |c|
43
- o << "<input type='hidden' name='fields[]' value='#{c}' />\n"
44
- end
45
- o << "<input type='submit' name='save' value='#{opts[:submit_text] || 'SAVE'}' />\n"
46
- o << "</form>\n"
47
- o
48
- end
49
- def backend_delete_form(url, opts={}); backend_form(url, [], {:submit_text=>'X', :method=>'DELETE'}.update(opts)){}; end
50
- def backend_clone_form(url, opts={})
51
- backend_form(url, [], {:submit_text=>'CLONE', :method=>'POST'}.update(opts)) do |out|
52
- out << "<input type='hidden' name='clone_id' value='#{self.id}' />\n"
53
- end
54
- end
55
- # Silly but usable form prototype
56
- # Not really meant to be used in a real case
57
- # It uses a textarea for everything
58
- # Override it
59
- # Or even better, use Sequel-Crushyform plugin instead
60
- def backend_fields(cols)
61
- o = ''
62
- cols.each do |c|
63
- identifier = "#{id}-#{self.class}-#{c}"
64
- o << "<label for='#{identifier}'>#{c.to_s.capitalize}</label><br />\n"
65
- o << "<textarea id='#{identifier}' name='model[#{c}]'>#{self[c]}</textarea><br />\n"
66
- end
67
- o
68
- end
69
- def backend_form_title; self.new? ? "New #{model.human_name}" : "Edit #{self.to_label}"; end
70
- def backend_show; 'OK'; end
71
-
72
- def default_backend_columns; model.schema.keys; end
73
- def cloning_backend_columns; default_backend_columns.reject{|c| model.schema[c][:type]==:attachment}; end
74
-
75
- end
76
- end
77
- end
78
-
@@ -1,243 +0,0 @@
1
- # encoding: utf-8
2
- module PopulateMe
3
- module Mongo
4
- module Crushyform
5
-
6
- # This module adds to mutated models the ability
7
- # to build forms using the settings of the schema
8
-
9
- def self.included(base)
10
- base.extend(ClassMethods)
11
- end
12
-
13
- module ClassMethods
14
-
15
- def crushyform_types
16
- @crushyform_types ||= {
17
- :none => proc{''},
18
- :string => proc do |m,c,o|
19
- if o[:autocompleted]
20
- values = o[:autocomplete_options] || m.class.collection.distinct(c)
21
- js = <<-EOJS
22
- <script type="text/javascript" charset="utf-8">
23
- $(function(){
24
- $( "##{m.field_id_for(c)}" ).autocomplete({source: ["#{values.map{|v|v.to_s.gsub(/"/,'')}.join('","')}"]});
25
- });
26
- </script>
27
- EOJS
28
- end
29
- tag = "<input type='%s' name='%s' value=\"%s\" id='%s' class='%s' %s />%s\n" % [o[:input_type]||'text', o[:input_name], o[:input_value], m.field_id_for(c), o[:input_class], o[:required]&&'required', o[:required]]
30
- "#{tag}#{js}"
31
- end,
32
- :slug => proc do |m,c,o|
33
- crushyform_types[:string].call(m,c,o)
34
- end,
35
- :price => proc do |m,c,o|
36
- value = o[:input_value].to_price_string if o[:input_value].respond_to?(:to_price_string)
37
- "<input type='%s' name='%s' value=\"%s\" id='%s' class='%s' %s />%s\n" % [o[:input_type]||'text', o[:input_name], value, m.field_id_for(c), o[:input_class], o[:required]&&'required', o[:required]]
38
- end,
39
- :boolean => proc do |m,c,o|
40
- crushid = m.field_id_for(c)
41
- checked = 'checked' if o[:input_value]
42
- out = "<input type='hidden' name='%s' value='false' id='%s-off' />\n"
43
- out += "<input type='checkbox' name='%s' value='true' id='%s' class='%s' %s />\n"
44
- out % [o[:input_name], crushid, o[:input_name], crushid, o[:input_class], checked]
45
- end,
46
- :text => proc do |m,c,o|
47
- "<textarea name='%s' id='%s' class='%s' %s>%s</textarea>%s\n" % [o[:input_name], m.field_id_for(c), o[:input_class], o[:required]&&'required', o[:input_value], o[:required]]
48
- end,
49
- :date => proc do |m,c,o|
50
- o[:input_value] = o[:input_value].strftime("%Y-%m-%d") if o[:input_value].respond_to?(:strftime)
51
- o[:required] = "%s Format: yyyy-mm-dd" % [o[:required]]
52
- crushyform_types[:string].call(m,c,o)
53
- end,
54
- :time => proc do |m,c,o|
55
- o[:input_value] = o[:input_value].strftime("%T") if o[:input_value].respond_to?(:strftime)
56
- o[:required] = "%s Format: hh:mm:ss" % [o[:required]]
57
- crushyform_types[:string].call(m,c,o)
58
- end,
59
- :datetime => proc do |m,c,o|
60
- o[:input_value] = o[:input_value].strftime("%Y-%m-%d %T") if o[:input_value].respond_to?(:strftime)
61
- o[:required] = "%s Format: yyyy-mm-dd hh:mm:ss" % [o[:required]]
62
- crushyform_types[:string].call(m,c,o)
63
- end,
64
- :parent => proc do |m,c,o|
65
- parent_class = o[:parent_class].nil? ? Kernel.const_get(c.sub(/^id_/, '')) : m.resolve_class(o[:parent_class])
66
- option_list = parent_class.to_dropdown(o[:input_value])
67
- "<select name='%s' id='%s' class='%s'>%s</select>\n" % [o[:input_name], m.field_id_for(c), o[:input_class], option_list]
68
- end,
69
- :children => proc do |m,c,o|
70
- children_class = o[:children_class].nil? ? Kernel.const_get(c.sub(/^ids_/, '')) : m.resolve_class(o[:children_class])
71
- opts = o.update({
72
- :multiple=>true,
73
- :select_options=>children_class.dropdown_cache
74
- })
75
- @crushyform_types[:select].call(m,c,opts)
76
- end,
77
- :attachment => proc do |m,c,o|
78
- deleter = "<input type='checkbox' name='#{o[:input_name]}' class='deleter' value='nil' /> Delete this file<br />" unless m.doc[c].nil?
79
- "%s<input type='file' name='%s' id='%s' class='%s' />%s %s\n" % [m.to_thumb(c), o[:input_name], m.field_id_for(c), o[:input_class], o[:required], deleter]
80
- end,
81
- :select => proc do |m,c,o|
82
- # starter ensures it sends something when multiple is empty
83
- # Otherwise it is not sent and therefore not updated
84
- starter = if o[:multiple]
85
- "<input type='hidden' name='%s[]' value='nil' class='multiple-select-starter' />\n" % [o[:input_name]]
86
- else
87
- ''
88
- end
89
- out = "%s<select name='%s%s' id='%s' class='%s' %s title='-- Select --'>\n" % [starter, o[:input_name], ('[]' if o[:multiple]), m.field_id_for(c), o[:input_class], ('multiple' if o[:multiple])]
90
- o[:select_options] = m.__send__(o[:select_options]) unless o[:select_options].kind_of?(Array)
91
- select_options = o[:select_options].dup
92
- if (o[:multiple] && !o[:input_value].nil? && o[:input_value].size>1)
93
- # This if is for having the selected options ordered (they can be ordered with asm select)
94
- o[:input_value].reverse.each do |v|
95
- elem = select_options.find{|x| x==v||(x||[])[1]==v }
96
- select_options.unshift(select_options.delete(elem)) unless elem.nil?
97
- end
98
- end
99
- if select_options.kind_of?(Array)
100
- select_options.each do |op|
101
- key,val = op.kind_of?(Array) ? [op[0],op[1]] : [op,op]
102
- if key==:optgroup
103
- out << "<optgroup label=\"%s\">\n" % [val]
104
- elsif key==:closegroup
105
- out << "</optgroup>\n"
106
- else
107
- # Array case is for multiple select
108
- selected = 'selected' if (val==o[:input_value] || (o[:input_value].kind_of?(Array)&&o[:input_value].include?(val)))
109
- out << "<option value=\"%s\" %s>%s</option>\n" % [val,selected,key]
110
- end
111
- end
112
- end
113
- out << "</select>%s\n" % [o[:required]]
114
- end,
115
- :string_list => proc do |m,c,o|
116
- if o[:autocompleted]
117
- values = o[:autocomplete_options] || m.class.collection.distinct(c)
118
- js = <<-EOJS
119
- <script type="text/javascript" charset="utf-8">
120
- $(function(){
121
- $( "##{m.field_id_for(c)}" )
122
- .bind( "keydown", function( event ) {
123
- if ( event.keyCode === $.ui.keyCode.TAB &&
124
- $( this ).data( "autocomplete" ).menu.active ) {
125
- event.preventDefault();
126
- }
127
- })
128
- .autocomplete({
129
- minLength: 0,
130
- source: function( request, response ) {
131
- response($.ui.autocomplete.filter(["#{values.map{|v|v.to_s.gsub(/"/,'')}.join('","')}"], request.term.split(/,\s*/).pop()));
132
- },
133
- focus: function() { return false; },
134
- select: function( event, ui ) {
135
- var terms = this.value.split(/,\s*/);
136
- terms.pop();
137
- terms.push(ui.item.value);
138
- terms.push("");
139
- this.value = terms.join( ", " );
140
- return false;
141
- }
142
- });
143
- });
144
- </script>
145
- EOJS
146
- o[:autocompleted] = false # reset so that it does not autocomplete for :string type below
147
- end
148
- tag = @crushyform_types[:string].call(m,c,o.update({:input_value=>(o[:input_value]||[]).join(',')}))
149
- "#{tag}#{js}"
150
- end,
151
- :permalink => proc do |instance, column_name, options|
152
- values = "<option value=''>Or Browse the list</option>\n"
153
- tag = @crushyform_types[:string].call(instance, column_name, options)
154
- return tag if options[:permalink_classes].nil?
155
- options[:permalink_classes].each do |sym|
156
- c = Kernel.const_get sym
157
- entries = c.find
158
- unless entries.count==0
159
- values << "<optgroup label='#{c.human_name}'>\n"
160
- entries.each do |e|
161
- values << "<option value='#{e.permalink}' #{'selected' if e.permalink==options[:input_value]}>#{e.to_label}</option>\n"
162
- end
163
- values << "</optgroup>\n"
164
- end
165
- end
166
- "#{tag}<br />\n<select name='__permalink' class='permalink-dropdown'>\n#{values}</select>\n"
167
- end
168
- }
169
- end
170
-
171
- # What represents a required field
172
- # Can be overriden
173
- def crushyfield_required; "<span class='crushyfield-required'> *</span>"; end
174
- # Stolen from ERB
175
- def html_escape(s)
176
- s.to_s.gsub(/&/, "&amp;").gsub(/\"/, "&quot;").gsub(/>/, "&gt;").gsub(/</, "&lt;")
177
- end
178
- # Cache dropdown options for children classes to use
179
- # Meant to be reseted each time an entry is created, updated or destroyed
180
- # So it is only rebuild once required after the list has changed
181
- # Maintaining an array and not rebuilding it all might be faster
182
- # But it will not happen much so that it is fairly acceptable
183
- def to_dropdown(selection=nil, nil_name='** UNDEFINED **')
184
- dropdown_cache.inject("<option value=''>#{nil_name}</option>\n") do |out, row|
185
- selected = 'selected' if row[1]==selection
186
- "%s%s%s%s" % [out, row[2], selected, row[3]]
187
- end
188
- end
189
- def dropdown_cache
190
- @dropdown_cache ||= self.find({},:fields=>['_id',label_column]).inject([]) do |out,row|
191
- out.push([row.to_label, row.id.to_s, "<option value=\"#{row.id}\" ", ">#{row.to_label}</option>\n"])
192
- end
193
- end
194
- def reset_dropdown_cache; @dropdown_cache = nil; end
195
-
196
- end
197
-
198
- # Instance Methods
199
-
200
- def crushyform(columns=model.schema.keys, action=nil, meth='POST')
201
- columns.delete('_id')
202
- fields = columns.inject(""){|out,c|out.force_encoding('utf-8')+crushyfield(c).force_encoding('utf-8')}
203
- enctype = fields.match(/type='file'/) ? "enctype='multipart/form-data'" : ''
204
- action.nil? ? fields : "<form action='%s' method='%s' %s>%s</form>\n" % [action, meth, enctype, fields]
205
- end
206
- # crushyfield is crushyinput but with label+error
207
- def crushyfield(col, o={})
208
- return '' if (o[:type]==:none || model.schema[col][:type]==:none)
209
- return crushyinput(col,o) if (o[:input_type]=='hidden' || model.schema[col][:input_type]=='hidden')
210
- default_field_name = col[/^id_/] ? Kernel.const_get(col.sub(/^id_/, '')).human_name : col.tr('_', ' ').capitalize
211
- field_name = o[:name] || model.schema[col][:name] || default_field_name
212
- if field_name.is_a?(Proc)
213
- field_name = field_name.(self)
214
- end
215
- error_list = errors_on(col).map{|e|" - #{e}"} if !errors_on(col).nil?
216
- "<p class='crushyfield %s'><label for='%s'>%s</label><span class='crushyfield-error-list'>%s</span><br />\n%s</p>\n" % [error_list&&'crushyfield-error', field_id_for(col), field_name, error_list, crushyinput(col, o)]
217
- end
218
- def crushyinput(col, o={})
219
- o = model.schema[col].dup.update(o)
220
- o[:input_name] ||= "model[#{col}]"
221
- o[:input_value] = o[:input_value].nil? ? self[col] : o[:input_value]
222
- o[:input_value] = model.html_escape(o[:input_value]) if (o[:input_value].is_a?(String) && o[:html_escape]!=false)
223
- o[:required] = o[:required]==true ? model.crushyfield_required : o[:required]
224
- crushyform_type = model.crushyform_types[o[:type]] || model.crushyform_types[:string]
225
- crushyform_type.call(self,col,o)
226
- end
227
- # Provide a thumbnail for the column
228
- def to_thumb(c)
229
- current = @doc[c]
230
- if current.respond_to?(:[])
231
- "<img src='/gridfs/#{@doc[c]['stash_thumb_gif']}' width='100' onerror=\"this.style.display='none'\" alt='Thumb' />\n"
232
- end
233
- end
234
- # Reset dropdowns on hooks
235
- def after_save; model.reset_dropdown_cache; super; end
236
- def after_delete; model.reset_dropdown_cache; super; end
237
- # Fix types
238
- def fix_type_string_list(k,v); @doc[k] = v.to_s.strip.split(/\s*,\s*/).compact if v.is_a?(String); end
239
-
240
-
241
- end
242
- end
243
- end
@@ -1,324 +0,0 @@
1
- # encoding: utf-8
2
- module PopulateMe
3
- module Mongo
4
- module Mutation
5
-
6
- # Most important MongoDB module
7
- # It defines the ODM
8
-
9
- def self.included(weak)
10
- weak.extend(MutateClass)
11
- weak.db = DB if defined?(DB)
12
- weak.schema = {}
13
- weak.relationships = {}
14
- end
15
-
16
- module MutateClass
17
- attr_accessor :db, :schema, :relationships
18
- attr_writer :label_column, :slug_column, :sorting_order
19
-
20
- LABEL_COLUMNS = ['title', 'label', 'fullname', 'full_name', 'surname', 'lastname', 'last_name', 'name', 'firstname', 'first_name', 'login', 'caption', 'reference', 'file_name', 'body', '_id']
21
- def label_column; @label_column ||= LABEL_COLUMNS.find{|c| @schema.keys.include?(c)||c=='_id'}; end
22
- def slug_column; @slug_column ||= (@schema.find{|k,v| v[:type]==:slug}||[])[0]; end
23
- def foreign_key_name(plural=false); "id#{'s' if plural}_"+self.name; end
24
- def human_name; self.name.gsub(/([A-Z])/, ' \1')[1..-1]; end
25
- def human_plural_name; human_name+'s'; end
26
- def collection; db[self.name]; end
27
- def correct_id_class(id)
28
- if id.is_a?(String)&&BSON::ObjectId.legal?(id)
29
- return BSON::ObjectId.from_string(id)
30
- elsif !id.is_a?(BSON::ObjectId)
31
- return ''
32
- end
33
- id
34
- end
35
- def ref(id)
36
- {'_id' => (id.kind_of?(Array) ? {'$in'=> id.map{|i|correct_id_class(i)} } : correct_id_class(id)) }
37
- end
38
- def find(selector={},opts={})
39
- selector.update(opts.delete(:selector)||{})
40
- opts = {:sort=>self.sorting_order}.update(opts)
41
- if opts.key?(:fields)
42
- opts[:projection] = opts[:fields].inject({}) do |h, f|
43
- h[f.to_sym] = 1
44
- h
45
- end
46
- opts.delete(:fields)
47
- end
48
- cur = collection.find(selector,opts)
49
- cur.instance_variable_set('@mutant_class', self)
50
- cur.extend(CursorMutation)
51
- end
52
- def find_one(spec_or_object_id=nil,opts={})
53
- spec_or_object_id.nil? ? spec_or_object_id = opts.delete(:selector) : spec_or_object_id.update(opts.delete(:selector)||{})
54
- opts = {:sort=>self.sorting_order}.update(opts)
55
- item = collection.find(spec_or_object_id,opts).first
56
- item.nil? ? nil : self.new(item)
57
- end
58
- def count(opts={}); collection.count(opts); end
59
-
60
- def sorting_order
61
- @sorting_order ||= if @schema.key?('position')&&!@schema['position'][:scope].nil?
62
- {@schema['position'][:scope] => 1, 'position' => 1}
63
- elsif @schema.key?('position')
64
- {'position' => 1, '_id' => 1}
65
- else
66
- {'_id' => 1}
67
- end
68
- end
69
-
70
- def sort(ids)
71
- requests = ids.each_with_index.inject([]) do |list, (id, i)|
72
- list << {update_one:
73
- {
74
- filter: ref(id),
75
- update: {'$set'=>{'position'=>i}}
76
- }
77
- }
78
- end
79
- collection.bulk_write requests
80
- end
81
-
82
- # CRUD
83
- def get(id, opts={}); doc = collection.find(ref(id), opts).first; doc.nil? ? nil : self.new(doc); end
84
- def delete(id); collection.delete_one(ref(id)); end
85
-
86
- def get_multiple(ids, opts={})
87
- corrected_ids = ids.map{|id| correct_id_class(id) }
88
- sort_proc = proc{ |a,b| corrected_ids.index(a['_id'])<=>corrected_ids.index(b['_id']) }
89
- self.find(ref(corrected_ids), opts).to_a.sort(&sort_proc)
90
- end
91
-
92
- def is_unique(doc={})
93
- return unless collection.count==0
94
- self.new(doc).save
95
- end
96
-
97
- private
98
- def slot(name,opts={})
99
- @schema[name] = {:type=>:string}.update(opts)
100
- define_method(name) { @doc[name] }
101
- define_method("#{name}=") { |x| @doc[name] = x }
102
- end
103
- def image_slot(name='image',opts={})
104
- slot name, {:type=>:attachment}.update(opts)
105
- slot "#{name}_tooltip"
106
- slot "#{name}_alternative_text"
107
- end
108
- def has_many(k,opts={}); @relationships[k] = opts; end
109
- end
110
-
111
- # Instance Methods
112
-
113
- attr_accessor :doc, :old_doc, :errors, :is_new
114
- def initialize(document=nil); @errors={}; @doc = document || default_doc; end
115
- def default_doc
116
- @is_new = true
117
- out = {}
118
- model.schema.each { |k,v| out.store(k,v[:default].is_a?(Proc) ? v[:default].call : v[:default]) }
119
- out
120
- end
121
- def model; self.class; end
122
- def id; @doc['_id']; end
123
- def [](field); @doc[field]; end
124
- def []=(field,val); @doc[field] = val; end
125
- def to_label; @doc[model.label_column].to_s.tr("\n\r", ' '); end
126
- ACCENTS_FROM =
127
- "ÀÁÂÃÄÅàáâãäåĀāĂ㥹ÇçĆćĈĉĊċČčÐðĎďĐđÈÉÊËèéêëĒēĔĕĖėĘęĚěĜĝĞğĠġĢģĤĥĦħÌÍÎÏìíîïĨĩĪīĬĭĮįİıĴĵĶķĸĹĺĻļĽľĿŀŁłÑñŃńŅņŇňʼnŊŋÒÓÔÕÖØòóôõöøŌōŎŏŐőŔŕŖŗŘřŚśŜŝŞ"
128
- ACCENTS_TO =
129
- "AAAAAAaaaaaaAaAaAaCcCcCcCcCcDdDdDdEEEEeeeeEeEeEeEeEeGgGgGgGgHhHhIIIIiiiiIiIiIiIiIiJjKkkLlLlLlLlLlNnNnNnNnnNnOOOOOOooooooOoOoOoRrRrRrSsSsSsSssT"
130
- def auto_slug
131
- s = self.to_label.tr(ACCENTS_FROM,ACCENTS_TO).tr(' .,;:?!/\'"()[]{}<>','-').gsub(/&/, 'and').gsub(/-+/,'-').gsub(/(^-|-$)/,'')
132
- defined?(::Rack::Utils) ? ::Rack::Utils.escape(s) : s
133
- end
134
- def to_slug; @doc[model.slug_column]||self.auto_slug; end
135
- # To param will be deprecated
136
- # Use a URL like .../<id>/<slug> instead
137
- def to_param; "#{@doc['_id']}-#{to_label.scan(/\w+/).join('-')}"; end
138
- def field_id_for(col); "%s-%s-%s" % [id||'new',model.name,col]; end
139
-
140
- # relationships
141
- def resolve_class(k); k.kind_of?(Class) ? k : Kernel.const_get(k); end
142
- def parent(k, opts={})
143
- if k.kind_of?(String)
144
- key = k
145
- klass = resolve_class(model.schema[k][:parent_class])
146
- else
147
- klass = resolve_class(k)
148
- key = klass.foreign_key_name
149
- end
150
- klass.get(@doc[key], opts)
151
- end
152
- def slot_children(k, opts={})
153
- if k.kind_of?(String)
154
- key = k
155
- klass = resolve_class(model.schema[k][:children_class])
156
- else
157
- klass = resolve_class(k)
158
- key = klass.foreign_key_name(true)
159
- end
160
- klass.get_multiple((@doc[key]||[]), opts)
161
- end
162
- def first_slot_child(k, opts={})
163
- if k.kind_of?(String)
164
- key = k
165
- klass = resolve_class(model.schema[k][:children_class])
166
- else
167
- klass = resolve_class(k)
168
- key = klass.foreign_key_name(true)
169
- end
170
- klass.get((@doc[key]||[])[0], opts)
171
- end
172
- def children(k,opts={})
173
- k = resolve_class(k)
174
- slot_name = opts.delete(:slot_name) || model.foreign_key_name
175
- k.find({slot_name=>@doc['_id'].to_s}, opts)
176
- end
177
- def first_child(k,opts={})
178
- k = resolve_class(k)
179
- slot_name = opts.delete(:slot_name) || model.foreign_key_name
180
- d = k.find_one({slot_name=>@doc['_id'].to_s}, opts)
181
- end
182
- def children_count(k,sel={})
183
- k = resolve_class(k)
184
- slot_name = sel.delete(:slot_name) || model.foreign_key_name
185
- k.collection.count({slot_name=>@doc['_id'].to_s}.update(sel))
186
- end
187
-
188
- # CRUD
189
- def delete
190
- before_delete
191
- model.delete(@doc['_id'])
192
- after_delete
193
- end
194
-
195
- # saving and hooks
196
- def new?; @is_new ||= !@doc.key?('_id'); end
197
- def update_doc(fields)
198
- @old_doc = @doc.dup
199
- @doc.update(fields)
200
- @is_new = false
201
- self
202
- end
203
- # Getter and setter in one
204
- def errors_on(col,message=nil)
205
- message.nil? ? @errors[col] : @errors[col] = (@errors[col]||[]) << message
206
- end
207
- def before_delete; @old_doc = @doc.dup; end
208
- alias before_destroy before_delete
209
- def after_delete
210
- model.relationships.each do |k,v|
211
- Kernel.const_get(k).find({model.foreign_key_name=>@old_doc['_id'].to_s}).each{|m| m.delete} unless v[:independent]
212
- end
213
- end
214
- alias after_destroy after_delete
215
- def valid?
216
- before_validation
217
- validate
218
- after_validation
219
- @errors.empty?
220
- end
221
- def before_validation
222
- @errors = {}
223
- @doc.each do |k,v|
224
- next unless model.schema.key?(k)
225
- type = k=='_id' ? :primary_key : model.schema[k][:type]
226
- fix_method = "fix_type_#{type}"
227
- if v=='' and type!=:attachment
228
- default = model.schema[k][:default]
229
- @doc[k] = default.is_a?(Proc) ? default.call : default
230
- else
231
- self.__send__(fix_method, k, v) if self.respond_to?(fix_method)
232
- end
233
- end
234
- end
235
- def validate; end
236
- def after_validation; end
237
- def fix_type_integer(k,v); @doc[k] = v.to_i; end
238
- def fix_type_price(k,v)
239
- @doc[k] = v.respond_to?(:to_price_integer) ? v.to_price_integer : v
240
- end
241
- def fix_type_boolean(k,v); @doc[k] = (v=='true'||v==true) ? true : false; end
242
- def fix_type_slug(k,v); @doc[k] = self.auto_slug if v.to_s==''; end
243
- def fix_type_date(k,v)
244
- if v.is_a?(String)
245
- if v[/\d\d\d\d-\d\d-\d\d/]
246
- @doc[k] = ::Time.utc(*v.split('-'))
247
- else
248
- default = model.schema[k][:default]
249
- @doc[k] = default.is_a?(Proc) ? default.call : default
250
- end
251
- end
252
- end
253
- def fix_type_datetime(k,v)
254
- if v.is_a?(String)
255
- if v[/\d\d\d\d-\d\d-\d\d \d\d:\d\d:\d\d/]
256
- @doc[k] = ::Time.utc(*v.split(/[-:\s]/))
257
- else
258
- default = model.schema[k][:default]
259
- @doc[k] = default.is_a?(Proc) ? default.call : default
260
- end
261
- end
262
- end
263
- def fix_type_select(k,v)
264
- if v.is_a?(Array)
265
- @doc[k] = v - ['nil']
266
- end
267
- end
268
- def fix_type_children(k,v)
269
- self.fix_type_select(k,v)
270
- end
271
-
272
- def save
273
- return nil unless valid?
274
- before_save
275
- if new?
276
- before_create
277
- result = model.collection.insert_one(@doc)
278
- if result.ok? and @doc['_id'].nil?
279
- @doc['_id'] = result.inserted_id
280
- end
281
- after_create
282
- else
283
- before_update
284
- result = model.collection.update_one({'_id'=>@doc['_id']}, @doc)
285
- after_update
286
- end
287
- after_save
288
- result.ok? ? self : nil
289
- end
290
- def before_save; end
291
- def before_create; end
292
- def before_update; end
293
- def after_save; end
294
- def after_create; @is_new = false; end
295
- def after_update; end
296
-
297
- # ==========
298
- # = Cursor =
299
- # ==========
300
- module CursorMutation
301
- # Extend the cursor provided by the Ruby MongoDB driver
302
- # We must keep the regular cursor
303
- # so we should extend on demand.
304
- # Meaning the cursor object should be extended, not the cursor class.
305
- # @mutant_class should be defined before extending
306
- #
307
- # def next
308
- # n = super
309
- # n.nil? ? nil : @mutant_class.new(n)
310
- # end
311
- #
312
- def each
313
- super do |doc|
314
- yield @mutant_class.new(doc)
315
- end if block_given?
316
- end
317
- # legacy
318
- def each_mutant(&b); each(&b); end
319
- def each_mutant_with_index(&b); each_with_index(&b); end
320
- end
321
-
322
- end
323
- end
324
- end