populate-me 0.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/README.md +9 -0
- data/lib/populate_me/control/_public/css/main.css +180 -0
- data/lib/populate_me/control/_public/css/plugin.asmselect.css +63 -0
- data/lib/populate_me/control/_public/css/ui-darkness/images/ui-bg_flat_30_cccccc_40x100.png +0 -0
- data/lib/populate_me/control/_public/css/ui-darkness/images/ui-bg_flat_50_5c5c5c_40x100.png +0 -0
- data/lib/populate_me/control/_public/css/ui-darkness/images/ui-bg_glass_20_555555_1x400.png +0 -0
- data/lib/populate_me/control/_public/css/ui-darkness/images/ui-bg_glass_40_0078a3_1x400.png +0 -0
- data/lib/populate_me/control/_public/css/ui-darkness/images/ui-bg_glass_40_ffc73d_1x400.png +0 -0
- data/lib/populate_me/control/_public/css/ui-darkness/images/ui-bg_gloss-wave_25_333333_500x100.png +0 -0
- data/lib/populate_me/control/_public/css/ui-darkness/images/ui-bg_highlight-soft_80_eeeeee_1x100.png +0 -0
- data/lib/populate_me/control/_public/css/ui-darkness/images/ui-bg_inset-soft_25_000000_1x100.png +0 -0
- data/lib/populate_me/control/_public/css/ui-darkness/images/ui-bg_inset-soft_30_f58400_1x100.png +0 -0
- data/lib/populate_me/control/_public/css/ui-darkness/images/ui-icons_222222_256x240.png +0 -0
- data/lib/populate_me/control/_public/css/ui-darkness/images/ui-icons_4b8e0b_256x240.png +0 -0
- data/lib/populate_me/control/_public/css/ui-darkness/images/ui-icons_a83300_256x240.png +0 -0
- data/lib/populate_me/control/_public/css/ui-darkness/images/ui-icons_cccccc_256x240.png +0 -0
- data/lib/populate_me/control/_public/css/ui-darkness/images/ui-icons_ffffff_256x240.png +0 -0
- data/lib/populate_me/control/_public/css/ui-darkness/jquery-ui-1.8.17.custom.css +430 -0
- data/lib/populate_me/control/_public/img/handle-pattern.png +0 -0
- data/lib/populate_me/control/_public/img/icons-cms.png +0 -0
- data/lib/populate_me/control/_public/img/placeholder.png +0 -0
- data/lib/populate_me/control/_public/img/placeholder.stash_thumb.gif +0 -0
- data/lib/populate_me/control/_public/img/placeholder.stash_thumb.png +0 -0
- data/lib/populate_me/control/_public/img/small-loader.gif +0 -0
- data/lib/populate_me/control/_public/js/addon.timepicker.js +20 -0
- data/lib/populate_me/control/_public/js/jquery-ui.js +114 -0
- data/lib/populate_me/control/_public/js/jquery.js +167 -0
- data/lib/populate_me/control/_public/js/jquery.mustache.js +559 -0
- data/lib/populate_me/control/_public/js/main.js +144 -0
- data/lib/populate_me/control/_public/js/plugin.asmselect.js +407 -0
- data/lib/populate_me/control/_public/js/plugin.form.js +11 -0
- data/lib/populate_me/control/_public/js/plugin.quicksearch.js +1 -0
- data/lib/populate_me/control/_public/js/plugin.underwood.js +4 -0
- data/lib/populate_me/control/views/layout.erb +77 -0
- data/lib/populate_me/control.rb +193 -0
- data/lib/populate_me/mongo/backend_api_plug.rb +77 -0
- data/lib/populate_me/mongo/crushyform.rb +225 -0
- data/lib/populate_me/mongo/mutation.rb +266 -0
- data/lib/populate_me/mongo/plug.rb +132 -0
- data/lib/populate_me/mongo/stash.rb +119 -0
- data/lib/populate_me/mongo.rb +1 -0
- data/populate-me.gemspec +14 -0
- metadata +120 -0
@@ -0,0 +1,11 @@
|
|
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);
|
@@ -0,0 +1 @@
|
|
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));
|
@@ -0,0 +1,4 @@
|
|
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
|
+
|
@@ -0,0 +1,77 @@
|
|
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'></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'></a>
|
31
|
+
</div>
|
32
|
+
<a href='<%= config[:path] %>/form/{{class_name}}/{{id}}' class='nutshell-edit pushstack'>
|
33
|
+
{{#thumb}}<img src='{{thumb}}' onerror="this.style.display='none'" />{{/thumb}}
|
34
|
+
{{^thumb}}<img src='/admin/_public/img/placeholder.png' onerror="this.style.display='none'" />{{/thumb}}
|
35
|
+
<span class='nutshell-title'>{{title}}</span>
|
36
|
+
</a>
|
37
|
+
<div class='nutshell-bottom'></div>
|
38
|
+
</div>
|
39
|
+
<ul class='nutshell-children'>
|
40
|
+
{{#children}}
|
41
|
+
<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>
|
42
|
+
{{/children}}
|
43
|
+
</ul>
|
44
|
+
</li>
|
45
|
+
</script>
|
46
|
+
</head>
|
47
|
+
<body>
|
48
|
+
<div id='trunk'>
|
49
|
+
<h1 id='page-title' class='shadowed'><%= config[:page_title] %></h1>
|
50
|
+
<div id='header' class='shadowed'>
|
51
|
+
<a href='#' class='btn back-btn float-left popstack'></a>
|
52
|
+
<a href='<%= config[:path] %>' class='btn home-btn float-right'></a>
|
53
|
+
<div id='search-wrap'><input type='text' id='search' placeholder='Search' /></div>
|
54
|
+
</div>
|
55
|
+
<noscript><div>In order to have this content management system working, you need to have javascript enabled.</div></noscript>
|
56
|
+
<div id='content'></div>
|
57
|
+
<div id='footer'>
|
58
|
+
© Mickaël Riga<br>
|
59
|
+
<a href='https://github.com/mig-hub/populate-me' target='_blank'>Populate Me</a> is an open source project
|
60
|
+
</div>
|
61
|
+
</div>
|
62
|
+
<script type="text/javascript" charset="utf-8">
|
63
|
+
var admin_path = "<%= config[:path] %>";
|
64
|
+
</script>
|
65
|
+
<!-- // <script src='http://code.jquery.com/jquery-1.6.2.min.js' type='text/javascript'></script> -->
|
66
|
+
<script src='<%= config[:path] %>/_public/js/jquery.js' type='text/javascript'></script>
|
67
|
+
<script src='<%= config[:path] %>/_public/js/jquery.mustache.js' type='text/javascript'></script>
|
68
|
+
<script src='<%= config[:path] %>/_public/js/jquery-ui.js' type='text/javascript'></script>
|
69
|
+
<script src='<%= config[:path] %>/_public/js/addon.timepicker.js' type='text/javascript'></script>
|
70
|
+
<script src='<%= config[:path] %>/_public/js/plugin.form.js' type='text/javascript'></script>
|
71
|
+
<script src='<%= config[:path] %>/_public/js/plugin.underwood.js' type='text/javascript'></script>
|
72
|
+
<script src='<%= config[:path] %>/_public/js/plugin.asmselect.js' type='text/javascript'></script>
|
73
|
+
<script src='<%= config[:path] %>/_public/js/plugin.quicksearch.js' type='text/javascript'></script>
|
74
|
+
<script src='<%= config[:path] %>/_public/js/main.js' type='text/javascript'></script>
|
75
|
+
</body>
|
76
|
+
</html>
|
77
|
+
|
@@ -0,0 +1,193 @@
|
|
1
|
+
require 'rack/golem'
|
2
|
+
require 'json'
|
3
|
+
|
4
|
+
module PopulateMe
|
5
|
+
module Control
|
6
|
+
F = ::File
|
7
|
+
DIR = F.expand_path(F.dirname(__FILE__)+'/control')
|
8
|
+
BEFORE = proc{
|
9
|
+
}
|
10
|
+
|
11
|
+
def self.included(klass)
|
12
|
+
klass.class_eval do
|
13
|
+
include ::Rack::Golem
|
14
|
+
extend ClassMethods
|
15
|
+
end
|
16
|
+
klass.before(&BEFORE)
|
17
|
+
end
|
18
|
+
|
19
|
+
module ClassMethods
|
20
|
+
def new(*)
|
21
|
+
::Rack::Static.new(::Rack::MethodOverride.new(super), :urls => ["/_public"], :root => DIR)
|
22
|
+
end
|
23
|
+
def config
|
24
|
+
@config ||= {
|
25
|
+
:client_name => 'Client Name',
|
26
|
+
:website_url => 'www.domain.com',
|
27
|
+
:page_title => 'Populate Me',
|
28
|
+
:path => '/admin',
|
29
|
+
:logout_path => '/admin/logout', # sometimes higher in stack
|
30
|
+
:menu => [['Home', '/admin']],
|
31
|
+
}
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def index(class_name=nil, id=nil)
|
36
|
+
if class_name.nil?
|
37
|
+
@r.env['erb.location'] = DIR+'/views/'
|
38
|
+
erb :layout
|
39
|
+
else
|
40
|
+
http_method = @r.request_method.downcase
|
41
|
+
if ['post','put','delete'].include?(http_method)
|
42
|
+
@model_class_name, @id = class_name, id
|
43
|
+
build_model_vars
|
44
|
+
return if @res.status==404
|
45
|
+
@res['Content-Type'] = 'text/json'
|
46
|
+
__send__(http_method)
|
47
|
+
else
|
48
|
+
send_404
|
49
|
+
'' # Trick for the moment but it needs a throw for 404
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def menu(*levels)
|
55
|
+
level_menu = config[:menu]
|
56
|
+
levels.each do |l|
|
57
|
+
level_menu = level_menu.assoc(l)[1]
|
58
|
+
end
|
59
|
+
items = level_menu.map do |i|
|
60
|
+
{
|
61
|
+
'title'=>i[0].gsub(/-/, ' '),
|
62
|
+
'href'=> i[1].kind_of?(String) ? i[1] : "#{config[:path]}/menu/#{levels.join('/')}/#{i[0]}"
|
63
|
+
}
|
64
|
+
end
|
65
|
+
page_title = levels.empty? ? config[:page_title] : levels.join(' / ').gsub(/-/, ' ')
|
66
|
+
|
67
|
+
@res['Content-Type'] = 'text/json'
|
68
|
+
JSON.generate({
|
69
|
+
'action'=>'menu',
|
70
|
+
'page_title'=>page_title,
|
71
|
+
'items'=>items
|
72
|
+
})
|
73
|
+
end
|
74
|
+
|
75
|
+
def list(class_name)
|
76
|
+
@model_class_name = class_name
|
77
|
+
resolve_model_class
|
78
|
+
items = @model_class.find(@r['filter']||{}).map {|m| m.to_nutshell }
|
79
|
+
|
80
|
+
@res['Content-Type'] = 'text/json'
|
81
|
+
JSON.generate({
|
82
|
+
'action'=> 'list',
|
83
|
+
'page_title'=> @model_class.human_plural_name,
|
84
|
+
'class_name'=> class_name,
|
85
|
+
'sortable'=> @model_class.sortable_on_that_page?(@r),
|
86
|
+
'command_plus'=> !@model_class.populate_config[:no_plus],
|
87
|
+
'command_search'=> !@model_class.populate_config[:no_search],
|
88
|
+
'items'=> items,
|
89
|
+
})
|
90
|
+
end
|
91
|
+
|
92
|
+
def form(class_name=nil, id=nil)
|
93
|
+
@model_class_name, @id = class_name, id
|
94
|
+
build_model_vars
|
95
|
+
return if @res.status==404
|
96
|
+
@model_instance ||= @model_class.backend_post
|
97
|
+
@model_instance.backend_put @r['model']
|
98
|
+
form = @model_instance.backend_form(@r.path.sub(/\/form/, ''), @r['fields'], :destination => @r['_destination'], :submit_text => @r['_submit_text'])
|
99
|
+
|
100
|
+
@res['Content-Type'] = 'text/json'
|
101
|
+
JSON.generate({
|
102
|
+
'action'=> 'form',
|
103
|
+
'page_title'=> @model_instance.backend_form_title,
|
104
|
+
'form'=>form
|
105
|
+
})
|
106
|
+
end
|
107
|
+
|
108
|
+
private
|
109
|
+
|
110
|
+
def config
|
111
|
+
@config ||= self.class.config.update({
|
112
|
+
:path => @r.script_name,
|
113
|
+
:logout_path => "#{@r.script_name}/logout"
|
114
|
+
}).dup
|
115
|
+
end
|
116
|
+
|
117
|
+
def resolve_model_class
|
118
|
+
if @model_class_name.nil?
|
119
|
+
raise("No model class name provided")
|
120
|
+
end
|
121
|
+
unless ::Object.const_defined?(@model_class_name)
|
122
|
+
raise("#{@model_class_name} is not constant")
|
123
|
+
end
|
124
|
+
@model_class = Kernel.const_get(@model_class_name)
|
125
|
+
unless @model_class.kind_of?(PopulateMe::Mongo::BackendApiPlug::ClassMethods)
|
126
|
+
raise("Requested constant #{@model_class_name} is not a model class")
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
def build_model_vars
|
131
|
+
resolve_model_class
|
132
|
+
@model_instance = @model_class.backend_get(@id) unless @id.nil?
|
133
|
+
@clone_instance = @model_class.backend_get(@r['clone_id']) unless @r['clone_id'].nil?
|
134
|
+
unless @clone_instance.nil?
|
135
|
+
@r['fields'] ||= @clone_instance.cloning_backend_columns
|
136
|
+
@r['model'] = @clone_instance.backend_values.reject{|k,v| !@r['fields'].include?(k.to_s)}
|
137
|
+
end
|
138
|
+
@r['model'] ||= {}
|
139
|
+
send_404 if @model_instance.nil?&&!@id.nil?
|
140
|
+
end
|
141
|
+
|
142
|
+
def post
|
143
|
+
return put unless @id.nil?
|
144
|
+
@model_instance = @model_class.backend_post(@r['model'])
|
145
|
+
save_and_respond
|
146
|
+
end
|
147
|
+
|
148
|
+
def put
|
149
|
+
if @id.nil? && @r[@model_class_name]
|
150
|
+
@model_class.sort(@r[@model_class_name])
|
151
|
+
JSON.generate({'ids'=>@r[@model_class_name]})
|
152
|
+
else
|
153
|
+
@model_instance.backend_put(@r['model'])
|
154
|
+
save_and_respond
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
158
|
+
def delete
|
159
|
+
@model_instance.backend_delete
|
160
|
+
@r['_destination'].nil? ? @res.status=204 : @res.redirect(::Rack::Utils::unescape(@r['_destination'])) # 204 No Content
|
161
|
+
end
|
162
|
+
|
163
|
+
def save_and_respond
|
164
|
+
if @model_instance.backend_save?
|
165
|
+
if @r['_destination'].nil?
|
166
|
+
@res.status=201 # Created
|
167
|
+
JSON.generate({
|
168
|
+
'action'=> 'save',
|
169
|
+
'message'=> 'ok'
|
170
|
+
})
|
171
|
+
else
|
172
|
+
@res.redirect(::Rack::Utils::unescape(@r['_destination']))
|
173
|
+
end
|
174
|
+
else
|
175
|
+
form = @model_instance.backend_form(@r.path, @r['fields']||@r['model'].keys, :destination => @r['_destination'], :submit_text => @r['_submit_text'])
|
176
|
+
# Bad Request 400 does not give content anymore in safari/lion
|
177
|
+
# So I put 200 back until I find a better code
|
178
|
+
JSON.generate({
|
179
|
+
'action'=> 'validation',
|
180
|
+
'form'=>form
|
181
|
+
})
|
182
|
+
end
|
183
|
+
end
|
184
|
+
|
185
|
+
def send_404
|
186
|
+
@res.status=404 # Not Found
|
187
|
+
@res.headers['X-Cascade']='pass'
|
188
|
+
@res.write 'Not Found'
|
189
|
+
end
|
190
|
+
|
191
|
+
end
|
192
|
+
end
|
193
|
+
|
@@ -0,0 +1,77 @@
|
|
1
|
+
module PopulateMe
|
2
|
+
module Mongo
|
3
|
+
module BackendApiPlug
|
4
|
+
|
5
|
+
# This module adds a layer between the backend API and the models.
|
6
|
+
# It is legacy code and could probably be removed,
|
7
|
+
# but in a way it makes it easier to plug something else than MongoDB.
|
8
|
+
# I'll keep it and see.
|
9
|
+
|
10
|
+
def self.included(base)
|
11
|
+
base.extend(ClassMethods)
|
12
|
+
end
|
13
|
+
|
14
|
+
module ClassMethods
|
15
|
+
def backend_get(id); get(id=='unique' ? '000000000000000000000000' : id); end
|
16
|
+
def backend_post(doc=nil); inst = new(doc); inst.is_new = true; inst; end
|
17
|
+
end
|
18
|
+
|
19
|
+
# Instance Methods
|
20
|
+
|
21
|
+
def backend_delete; delete; end
|
22
|
+
def backend_put(fields); update_doc(fields); end
|
23
|
+
def backend_values; @doc; end
|
24
|
+
def backend_save?; !save.nil?; end
|
25
|
+
def backend_form(url, cols=nil, opts={})
|
26
|
+
cols ||= default_backend_columns
|
27
|
+
if block_given?
|
28
|
+
fields_list = ''
|
29
|
+
yield(fields_list)
|
30
|
+
else
|
31
|
+
fields_list = respond_to?(:crushyform) ? crushyform(cols) : backend_fields(cols)
|
32
|
+
end
|
33
|
+
o = "<form action='#{url}' method='POST' #{"enctype='multipart/form-data'" if fields_list.match(/type='file'/)} class='backend-form'>\n"
|
34
|
+
#o << backend_form_title unless block_given?
|
35
|
+
o << fields_list
|
36
|
+
opts[:method] = 'PUT' if (opts[:method].nil? && !self.new?)
|
37
|
+
o << "<input type='hidden' name='_method' value='#{opts[:method]}' />\n" unless opts[:method].nil?
|
38
|
+
o << "<input type='hidden' name='_destination' value='#{opts[:destination]}' />\n" unless opts[:destination].nil?
|
39
|
+
o << "<input type='hidden' name='_submit_text' value='#{opts[:submit_text]}' />\n" unless opts[:submit_text].nil?
|
40
|
+
o << "<input type='hidden' name='_no_wrap' value='#{opts[:no_wrap]}' />\n" unless opts[:no_wrap].nil?
|
41
|
+
cols.each do |c|
|
42
|
+
o << "<input type='hidden' name='fields[]' value='#{c}' />\n"
|
43
|
+
end
|
44
|
+
o << "<input type='submit' name='save' value='#{opts[:submit_text] || 'SAVE'}' />\n"
|
45
|
+
o << "</form>\n"
|
46
|
+
o
|
47
|
+
end
|
48
|
+
def backend_delete_form(url, opts={}); backend_form(url, [], {:submit_text=>'X', :method=>'DELETE'}.update(opts)){}; end
|
49
|
+
def backend_clone_form(url, opts={})
|
50
|
+
backend_form(url, [], {:submit_text=>'CLONE', :method=>'POST'}.update(opts)) do |out|
|
51
|
+
out << "<input type='hidden' name='clone_id' value='#{self.id}' />\n"
|
52
|
+
end
|
53
|
+
end
|
54
|
+
# Silly but usable form prototype
|
55
|
+
# Not really meant to be used in a real case
|
56
|
+
# It uses a textarea for everything
|
57
|
+
# Override it
|
58
|
+
# Or even better, use Sequel-Crushyform plugin instead
|
59
|
+
def backend_fields(cols)
|
60
|
+
o = ''
|
61
|
+
cols.each do |c|
|
62
|
+
identifier = "#{id}-#{self.class}-#{c}"
|
63
|
+
o << "<label for='#{identifier}'>#{c.to_s.capitalize}</label><br />\n"
|
64
|
+
o << "<textarea id='#{identifier}' name='model[#{c}]'>#{self[c]}</textarea><br />\n"
|
65
|
+
end
|
66
|
+
o
|
67
|
+
end
|
68
|
+
def backend_form_title; self.new? ? "New #{model.human_name}" : "Edit #{self.to_label}"; end
|
69
|
+
def backend_show; 'OK'; end
|
70
|
+
|
71
|
+
def default_backend_columns; model.schema.keys; end
|
72
|
+
def cloning_backend_columns; default_backend_columns.reject{|c| model.schema[c][:type]==:attachment}; end
|
73
|
+
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
@@ -0,0 +1,225 @@
|
|
1
|
+
module PopulateMe
|
2
|
+
module Mongo
|
3
|
+
module Crushyform
|
4
|
+
|
5
|
+
# This module adds to mutated models the ability
|
6
|
+
# to build forms using the settings of the schema
|
7
|
+
|
8
|
+
def self.included(base)
|
9
|
+
base.extend(ClassMethods)
|
10
|
+
end
|
11
|
+
|
12
|
+
module ClassMethods
|
13
|
+
|
14
|
+
def crushyform_types
|
15
|
+
@crushyform_types ||= {
|
16
|
+
:none => proc{''},
|
17
|
+
:string => proc do |m,c,o|
|
18
|
+
if o[:autocompleted]
|
19
|
+
values = o[:autocomplete_options] || m.class.collection.distinct(c)
|
20
|
+
js = <<-EOJS
|
21
|
+
<script type="text/javascript" charset="utf-8">
|
22
|
+
$(function(){
|
23
|
+
$( "##{m.field_id_for(c)}" ).autocomplete({source: ["#{values.join('","')}"]});
|
24
|
+
});
|
25
|
+
</script>
|
26
|
+
EOJS
|
27
|
+
end
|
28
|
+
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]]
|
29
|
+
"#{tag}#{js}"
|
30
|
+
end,
|
31
|
+
:boolean => proc do |m,c,o|
|
32
|
+
crushid = m.field_id_for(c)
|
33
|
+
s = ['checked', nil]
|
34
|
+
s.reverse! unless o[:input_value]
|
35
|
+
out = "<span class='%s'>"
|
36
|
+
out += "<input type='radio' name='%s' value='true' id='%s' %s /> <label for='%s'>Yes</label> "
|
37
|
+
out += "<input type='radio' name='%s' value='false' id='%s-no' %s /> <label for='%s-no'>No</label>"
|
38
|
+
out += "</span>\n"
|
39
|
+
out % [o[:input_class], o[:input_name], crushid, s[0], crushid, o[:input_name], crushid, s[1], crushid]
|
40
|
+
end,
|
41
|
+
:text => proc do |m,c,o|
|
42
|
+
"<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]]
|
43
|
+
end,
|
44
|
+
:date => proc do |m,c,o|
|
45
|
+
o[:input_value] = o[:input_value].strftime("%Y-%m-%d") if o[:input_value].respond_to?(:strftime)
|
46
|
+
o[:required] = "%s Format: yyyy-mm-dd" % [o[:required]]
|
47
|
+
crushyform_types[:string].call(m,c,o)
|
48
|
+
end,
|
49
|
+
:time => proc do |m,c,o|
|
50
|
+
o[:input_value] = o[:input_value].strftime("%T") if o[:input_value].respond_to?(:strftime)
|
51
|
+
o[:required] = "%s Format: hh:mm:ss" % [o[:required]]
|
52
|
+
crushyform_types[:string].call(m,c,o)
|
53
|
+
end,
|
54
|
+
:datetime => proc do |m,c,o|
|
55
|
+
o[:input_value] = o[:input_value].strftime("%Y-%m-%d %T") if o[:input_value].respond_to?(:strftime)
|
56
|
+
o[:required] = "%s Format: yyyy-mm-dd hh:mm:ss" % [o[:required]]
|
57
|
+
crushyform_types[:string].call(m,c,o)
|
58
|
+
end,
|
59
|
+
:parent => proc do |m,c,o|
|
60
|
+
parent_class = o[:parent_class].nil? ? Kernel.const_get(c.sub(/^id_/, '')) : m.resolve_class(o[:parent_class])
|
61
|
+
option_list = parent_class.to_dropdown(o[:input_value])
|
62
|
+
"<select name='%s' id='%s' class='%s'>%s</select>\n" % [o[:input_name], m.field_id_for(c), o[:input_class], option_list]
|
63
|
+
end,
|
64
|
+
:children => proc do |m,c,o|
|
65
|
+
children_class = o[:children_class].nil? ? Kernel.const_get(c.sub(/^ids_/, '')) : m.resolve_class(o[:children_class])
|
66
|
+
opts = o.update({
|
67
|
+
:multiple=>true,
|
68
|
+
:select_options=>children_class.dropdown_cache
|
69
|
+
})
|
70
|
+
@crushyform_types[:select].call(m,c,opts)
|
71
|
+
end,
|
72
|
+
:attachment => proc do |m,c,o|
|
73
|
+
deleter = "<input type='checkbox' name='#{o[:input_name]}' class='deleter' value='nil' /> Delete this file<br />" unless m.doc[c].nil?
|
74
|
+
"%s%s<input type='file' name='%s' id='%s' class='%s' />%s\n" % [m.to_thumb(c), deleter, o[:input_name], m.field_id_for(c), o[:input_class], o[:required]]
|
75
|
+
end,
|
76
|
+
:select => proc do |m,c,o|
|
77
|
+
out = "<select name='%s%s' id='%s' class='%s' %s title='-- Select --'>\n" % [o[:input_name], ('[]' if o[:multiple]), m.field_id_for(c), o[:input_class], ('multiple' if o[:multiple])]
|
78
|
+
o[:select_options] = m.__send__(o[:select_options]) unless o[:select_options].kind_of?(Array)
|
79
|
+
select_options = o[:select_options].dup
|
80
|
+
if (o[:multiple] && !o[:input_value].nil? && o[:input_value].size>1)
|
81
|
+
o[:input_value].reverse.each do |v|
|
82
|
+
elem = select_options.find{|x| x==v||(x||[])[1]==v }
|
83
|
+
select_options.unshift(select_options.delete(elem)) unless elem.nil?
|
84
|
+
end
|
85
|
+
end
|
86
|
+
if select_options.kind_of?(Array)
|
87
|
+
select_options.each do |op|
|
88
|
+
key,val = op.kind_of?(Array) ? [op[0],op[1]] : [op,op]
|
89
|
+
if key==:optgroup
|
90
|
+
out << "<optgroup label='%s'>\n" % [val]
|
91
|
+
elsif key==:closegroup
|
92
|
+
out << "</optgroup>\n"
|
93
|
+
else
|
94
|
+
selected = 'selected' if (val==o[:input_value] || (o[:input_value]||[]).include?(val))
|
95
|
+
out << "<option value='%s' %s>%s</option>\n" % [val,selected,key]
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
out << "</select>%s\n" % [o[:required]]
|
100
|
+
end,
|
101
|
+
:string_list => proc do |m,c,o|
|
102
|
+
if o[:autocompleted]
|
103
|
+
values = o[:autocomplete_options] || m.class.collection.distinct(c)
|
104
|
+
js = <<-EOJS
|
105
|
+
<script type="text/javascript" charset="utf-8">
|
106
|
+
$(function(){
|
107
|
+
$( "##{m.field_id_for(c)}" )
|
108
|
+
.bind( "keydown", function( event ) {
|
109
|
+
if ( event.keyCode === $.ui.keyCode.TAB &&
|
110
|
+
$( this ).data( "autocomplete" ).menu.active ) {
|
111
|
+
event.preventDefault();
|
112
|
+
}
|
113
|
+
})
|
114
|
+
.autocomplete({
|
115
|
+
minLength: 0,
|
116
|
+
source: function( request, response ) {
|
117
|
+
response($.ui.autocomplete.filter(["#{values.join('","')}"], request.term.split(/,\s*/).pop()));
|
118
|
+
},
|
119
|
+
focus: function() { return false; },
|
120
|
+
select: function( event, ui ) {
|
121
|
+
var terms = this.value.split(/,\s*/);
|
122
|
+
terms.pop();
|
123
|
+
terms.push(ui.item.value);
|
124
|
+
terms.push("");
|
125
|
+
this.value = terms.join( ", " );
|
126
|
+
return false;
|
127
|
+
}
|
128
|
+
});
|
129
|
+
});
|
130
|
+
</script>
|
131
|
+
EOJS
|
132
|
+
o[:autocompleted] = false # reset so that it does not autocomplete for :string type below
|
133
|
+
end
|
134
|
+
tag = @crushyform_types[:string].call(m,c,o.update({:input_value=>(o[:input_value]||[]).join(',')}))
|
135
|
+
"#{tag}#{js}"
|
136
|
+
end,
|
137
|
+
:permalink => proc do |instance, column_name, options|
|
138
|
+
values = "<option value=''>Or Browse the list</option>\n"
|
139
|
+
tag = @crushyform_types[:string].call(instance, column_name, options)
|
140
|
+
return tag if options[:permalink_classes].nil?
|
141
|
+
options[:permalink_classes].each do |sym|
|
142
|
+
c = Kernel.const_get sym
|
143
|
+
entries = c.collection.find
|
144
|
+
unless entries.count==0
|
145
|
+
values << "<optgroup label='#{c.human_name}'>\n"
|
146
|
+
entries.each_mutant do |e|
|
147
|
+
values << "<option value='#{e.permalink}' #{'selected' if e.permalink==options[:input_value]}>#{e.to_label}</option>\n"
|
148
|
+
end
|
149
|
+
values << "</optgroup>\n"
|
150
|
+
end
|
151
|
+
end
|
152
|
+
"#{tag}<br />\n<select name='__permalink' class='permalink-dropdown'>\n#{values}</select>\n"
|
153
|
+
end
|
154
|
+
}
|
155
|
+
end
|
156
|
+
|
157
|
+
# What represents a required field
|
158
|
+
# Can be overriden
|
159
|
+
def crushyfield_required; "<span class='crushyfield-required'> *</span>"; end
|
160
|
+
# Stolen from ERB
|
161
|
+
def html_escape(s)
|
162
|
+
s.to_s.gsub(/&/, "&").gsub(/\"/, """).gsub(/>/, ">").gsub(/</, "<")
|
163
|
+
end
|
164
|
+
# Cache dropdown options for children classes to use
|
165
|
+
# Meant to be reseted each time an entry is created, updated or destroyed
|
166
|
+
# So it is only rebuild once required after the list has changed
|
167
|
+
# Maintaining an array and not rebuilding it all might be faster
|
168
|
+
# But it will not happen much so that it is fairly acceptable
|
169
|
+
def to_dropdown(selection=nil, nil_name='** UNDEFINED **')
|
170
|
+
dropdown_cache.inject("<option value=''>#{nil_name}</option>\n") do |out, row|
|
171
|
+
selected = 'selected' if row[1]==selection
|
172
|
+
"%s%s%s%s" % [out, row[2], selected, row[3]]
|
173
|
+
end
|
174
|
+
end
|
175
|
+
def dropdown_cache
|
176
|
+
@dropdown_cache ||= self.find({},:fields=>['_id',label_column]).inject([]) do |out,row|
|
177
|
+
out.push([row.to_label, row.id.to_s, "<option value='#{row.id}' ", ">#{row.to_label}</option>\n"])
|
178
|
+
end
|
179
|
+
end
|
180
|
+
def reset_dropdown_cache; @dropdown_cache = nil; end
|
181
|
+
|
182
|
+
end
|
183
|
+
|
184
|
+
# Instance Methods
|
185
|
+
|
186
|
+
def crushyform(columns=model.schema.keys, action=nil, meth='POST')
|
187
|
+
columns.delete('_id')
|
188
|
+
fields = columns.inject(""){|out,c|out+crushyfield(c)}
|
189
|
+
enctype = fields.match(/type='file'/) ? "enctype='multipart/form-data'" : ''
|
190
|
+
action.nil? ? fields : "<form action='%s' method='%s' %s>%s</form>\n" % [action, meth, enctype, fields]
|
191
|
+
end
|
192
|
+
# crushyfield is crushyinput but with label+error
|
193
|
+
def crushyfield(col, o={})
|
194
|
+
return '' if (o[:type]==:none || model.schema[col][:type]==:none)
|
195
|
+
default_field_name = col[/^id_/] ? Kernel.const_get(col.sub(/^id_/, '')).human_name : col.tr('_', ' ').capitalize
|
196
|
+
field_name = o[:name] || model.schema[col][:name] || default_field_name
|
197
|
+
error_list = errors_on(col).map{|e|" - #{e}"} if !errors_on(col).nil?
|
198
|
+
"<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)]
|
199
|
+
end
|
200
|
+
def crushyinput(col, o={})
|
201
|
+
o = model.schema[col].dup.update(o)
|
202
|
+
o[:input_name] ||= "model[#{col}]"
|
203
|
+
o[:input_value] = o[:input_value].nil? ? self[col] : o[:input_value]
|
204
|
+
o[:input_value] = model.html_escape(o[:input_value]) if (o[:input_value].is_a?(String) && o[:html_escape]!=false)
|
205
|
+
o[:required] = o[:required]==true ? model.crushyfield_required : o[:required]
|
206
|
+
crushyform_type = model.crushyform_types[o[:type]] || model.crushyform_types[:string]
|
207
|
+
crushyform_type.call(self,col,o)
|
208
|
+
end
|
209
|
+
# Provide a thumbnail for the column
|
210
|
+
def to_thumb(c)
|
211
|
+
current = @doc[c]
|
212
|
+
if current.respond_to?(:[])
|
213
|
+
"<img src='/gridfs/#{@doc[c]['stash_thumb.gif']}' width='100' onerror=\"this.style.display='none'\" />\n"
|
214
|
+
end
|
215
|
+
end
|
216
|
+
# Reset dropdowns on hooks
|
217
|
+
def after_save; model.reset_dropdown_cache; super; end
|
218
|
+
def after_delete; model.reset_dropdown_cache; super; end
|
219
|
+
# Fix types
|
220
|
+
def fix_type_string_list(k,v); @doc[k] = v.to_s.strip.split(/\s*,\s*/).compact if v.is_a?(String); end
|
221
|
+
|
222
|
+
|
223
|
+
end
|
224
|
+
end
|
225
|
+
end
|