populate-me 0.0.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|