formotion 0.5.1 → 1.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.
Files changed (85) hide show
  1. data/CHANGELOG.md +24 -0
  2. data/LIST_OF_ROW_TYPES.md +1 -1
  3. data/Rakefile +20 -5
  4. data/app/app_delegate.rb +89 -17
  5. data/examples/FormModel/.gitignore +5 -0
  6. data/examples/FormModel/Rakefile +9 -0
  7. data/examples/FormModel/app/app_delegate.rb +11 -0
  8. data/examples/FormModel/app/user.rb +18 -0
  9. data/examples/FormModel/app/users_controller.rb +52 -0
  10. data/examples/FormModel/spec/main_spec.rb +9 -0
  11. data/examples/KitchenSink/app/app_delegate.rb +2 -2
  12. data/gh-pages/assets/css/bootstrap-responsive.css +815 -0
  13. data/gh-pages/assets/css/bootstrap-responsive.min.css +9 -0
  14. data/gh-pages/assets/css/bootstrap.css +4983 -0
  15. data/gh-pages/assets/css/bootstrap.min.css +9 -0
  16. data/gh-pages/assets/css/formotion.css +117 -0
  17. data/gh-pages/assets/css/pygments.css +62 -0
  18. data/gh-pages/assets/img/glyphicons-halflings-white.png +0 -0
  19. data/gh-pages/assets/img/glyphicons-halflings.png +0 -0
  20. data/gh-pages/assets/js/bootstrap-alert.js +90 -0
  21. data/gh-pages/assets/js/bootstrap-button.js +96 -0
  22. data/gh-pages/assets/js/bootstrap-carousel.js +169 -0
  23. data/gh-pages/assets/js/bootstrap-collapse.js +157 -0
  24. data/gh-pages/assets/js/bootstrap-dropdown.js +100 -0
  25. data/gh-pages/assets/js/bootstrap-modal.js +218 -0
  26. data/gh-pages/assets/js/bootstrap-popover.js +98 -0
  27. data/gh-pages/assets/js/bootstrap-scrollspy.js +151 -0
  28. data/gh-pages/assets/js/bootstrap-tab.js +135 -0
  29. data/gh-pages/assets/js/bootstrap-tooltip.js +275 -0
  30. data/gh-pages/assets/js/bootstrap-transition.js +61 -0
  31. data/gh-pages/assets/js/bootstrap-typeahead.js +285 -0
  32. data/gh-pages/assets/js/bootstrap.js +1825 -0
  33. data/gh-pages/assets/js/bootstrap.min.js +6 -0
  34. data/gh-pages/index.html +205 -0
  35. data/lib/formotion.rb +15 -2
  36. data/lib/formotion/base.rb +5 -5
  37. data/lib/formotion/{form_controller.rb → controller/form_controller.rb} +22 -3
  38. data/lib/formotion/controller/formable_controller.rb +21 -0
  39. data/lib/formotion/form/form.rb +31 -8
  40. data/lib/formotion/form/form_delegate.rb +26 -3
  41. data/lib/formotion/model/formable.rb +78 -0
  42. data/lib/formotion/row/row.rb +54 -21
  43. data/lib/formotion/row/row_cell_builder.rb +1 -1
  44. data/lib/formotion/row_type/back_row.rb +11 -0
  45. data/lib/formotion/row_type/base.rb +42 -1
  46. data/lib/formotion/row_type/button.rb +30 -0
  47. data/lib/formotion/row_type/check_row.rb +9 -5
  48. data/lib/formotion/row_type/date_row.rb +5 -0
  49. data/lib/formotion/row_type/edit_row.rb +14 -0
  50. data/lib/formotion/row_type/image_row.rb +7 -7
  51. data/lib/formotion/row_type/options_row.rb +18 -8
  52. data/lib/formotion/row_type/slider_row.rb +14 -5
  53. data/lib/formotion/row_type/string_row.rb +17 -5
  54. data/lib/formotion/row_type/subform_row.rb +16 -0
  55. data/lib/formotion/row_type/submit_row.rb +1 -24
  56. data/lib/formotion/row_type/switch_row.rb +10 -2
  57. data/lib/formotion/row_type/template_row.rb +77 -0
  58. data/lib/formotion/row_type/text_row.rb +12 -4
  59. data/lib/formotion/section/section.rb +9 -1
  60. data/lib/formotion/version.rb +1 -1
  61. data/spec/form_spec.rb +24 -0
  62. data/spec/formable_spec.rb +100 -0
  63. data/spec/functional/formable_controller_spec.rb +47 -0
  64. data/spec/functional/image_row_spec.rb +2 -2
  65. data/spec/functional/subform_row.rb +33 -0
  66. data/spec/functional/template_row_spec.rb +57 -0
  67. data/spec/helpers/row_test_helpers.rb +26 -0
  68. data/spec/row_type/back_spec.rb +31 -0
  69. data/spec/row_type/check_spec.rb +9 -9
  70. data/spec/row_type/date_spec.rb +3 -10
  71. data/spec/row_type/email_spec.rb +1 -9
  72. data/spec/row_type/image_spec.rb +3 -12
  73. data/spec/row_type/number_spec.rb +1 -9
  74. data/spec/row_type/options_spec.rb +18 -10
  75. data/spec/row_type/phone_spec.rb +1 -9
  76. data/spec/row_type/picker_spec.rb +2 -11
  77. data/spec/row_type/slider_spec.rb +11 -10
  78. data/spec/row_type/static_spec.rb +1 -9
  79. data/spec/row_type/string_spec.rb +13 -9
  80. data/spec/row_type/subform_spec.rb +47 -0
  81. data/spec/row_type/submit_spec.rb +1 -9
  82. data/spec/row_type/switch_spec.rb +9 -9
  83. data/spec/row_type/template_spec.rb +14 -0
  84. data/spec/row_type/text_spec.rb +9 -9
  85. metadata +58 -6
@@ -0,0 +1,6 @@
1
+ /*!
2
+ * Bootstrap.js by @fat & @mdo
3
+ * Copyright 2012 Twitter, Inc.
4
+ * http://www.apache.org/licenses/LICENSE-2.0.txt
5
+ */
6
+ !function(a){a(function(){"use strict",a.support.transition=function(){var a=function(){var a=document.createElement("bootstrap"),b={WebkitTransition:"webkitTransitionEnd",MozTransition:"transitionend",OTransition:"oTransitionEnd",msTransition:"MSTransitionEnd",transition:"transitionend"},c;for(c in b)if(a.style[c]!==undefined)return b[c]}();return a&&{end:a}}()})}(window.jQuery),!function(a){"use strict";var b='[data-dismiss="alert"]',c=function(c){a(c).on("click",b,this.close)};c.prototype.close=function(b){function f(){e.trigger("closed").remove()}var c=a(this),d=c.attr("data-target"),e;d||(d=c.attr("href"),d=d&&d.replace(/.*(?=#[^\s]*$)/,"")),e=a(d),b&&b.preventDefault(),e.length||(e=c.hasClass("alert")?c:c.parent()),e.trigger(b=a.Event("close"));if(b.isDefaultPrevented())return;e.removeClass("in"),a.support.transition&&e.hasClass("fade")?e.on(a.support.transition.end,f):f()},a.fn.alert=function(b){return this.each(function(){var d=a(this),e=d.data("alert");e||d.data("alert",e=new c(this)),typeof b=="string"&&e[b].call(d)})},a.fn.alert.Constructor=c,a(function(){a("body").on("click.alert.data-api",b,c.prototype.close)})}(window.jQuery),!function(a){"use strict";var b=function(b,c){this.$element=a(b),this.options=a.extend({},a.fn.button.defaults,c)};b.prototype.setState=function(a){var b="disabled",c=this.$element,d=c.data(),e=c.is("input")?"val":"html";a+="Text",d.resetText||c.data("resetText",c[e]()),c[e](d[a]||this.options[a]),setTimeout(function(){a=="loadingText"?c.addClass(b).attr(b,b):c.removeClass(b).removeAttr(b)},0)},b.prototype.toggle=function(){var a=this.$element.parent('[data-toggle="buttons-radio"]');a&&a.find(".active").removeClass("active"),this.$element.toggleClass("active")},a.fn.button=function(c){return this.each(function(){var d=a(this),e=d.data("button"),f=typeof c=="object"&&c;e||d.data("button",e=new b(this,f)),c=="toggle"?e.toggle():c&&e.setState(c)})},a.fn.button.defaults={loadingText:"loading..."},a.fn.button.Constructor=b,a(function(){a("body").on("click.button.data-api","[data-toggle^=button]",function(b){var c=a(b.target);c.hasClass("btn")||(c=c.closest(".btn")),c.button("toggle")})})}(window.jQuery),!function(a){"use strict";var b=function(b,c){this.$element=a(b),this.options=c,this.options.slide&&this.slide(this.options.slide),this.options.pause=="hover"&&this.$element.on("mouseenter",a.proxy(this.pause,this)).on("mouseleave",a.proxy(this.cycle,this))};b.prototype={cycle:function(b){return b||(this.paused=!1),this.options.interval&&!this.paused&&(this.interval=setInterval(a.proxy(this.next,this),this.options.interval)),this},to:function(b){var c=this.$element.find(".active"),d=c.parent().children(),e=d.index(c),f=this;if(b>d.length-1||b<0)return;return this.sliding?this.$element.one("slid",function(){f.to(b)}):e==b?this.pause().cycle():this.slide(b>e?"next":"prev",a(d[b]))},pause:function(a){return a||(this.paused=!0),clearInterval(this.interval),this.interval=null,this},next:function(){if(this.sliding)return;return this.slide("next")},prev:function(){if(this.sliding)return;return this.slide("prev")},slide:function(b,c){var d=this.$element.find(".active"),e=c||d[b](),f=this.interval,g=b=="next"?"left":"right",h=b=="next"?"first":"last",i=this,j=a.Event("slide");this.sliding=!0,f&&this.pause(),e=e.length?e:this.$element.find(".item")[h]();if(e.hasClass("active"))return;if(a.support.transition&&this.$element.hasClass("slide")){this.$element.trigger(j);if(j.isDefaultPrevented())return;e.addClass(b),e[0].offsetWidth,d.addClass(g),e.addClass(g),this.$element.one(a.support.transition.end,function(){e.removeClass([b,g].join(" ")).addClass("active"),d.removeClass(["active",g].join(" ")),i.sliding=!1,setTimeout(function(){i.$element.trigger("slid")},0)})}else{this.$element.trigger(j);if(j.isDefaultPrevented())return;d.removeClass("active"),e.addClass("active"),this.sliding=!1,this.$element.trigger("slid")}return f&&this.cycle(),this}},a.fn.carousel=function(c){return this.each(function(){var d=a(this),e=d.data("carousel"),f=a.extend({},a.fn.carousel.defaults,typeof c=="object"&&c);e||d.data("carousel",e=new b(this,f)),typeof c=="number"?e.to(c):typeof c=="string"||(c=f.slide)?e[c]():f.interval&&e.cycle()})},a.fn.carousel.defaults={interval:5e3,pause:"hover"},a.fn.carousel.Constructor=b,a(function(){a("body").on("click.carousel.data-api","[data-slide]",function(b){var c=a(this),d,e=a(c.attr("data-target")||(d=c.attr("href"))&&d.replace(/.*(?=#[^\s]+$)/,"")),f=!e.data("modal")&&a.extend({},e.data(),c.data());e.carousel(f),b.preventDefault()})})}(window.jQuery),!function(a){"use strict";var b=function(b,c){this.$element=a(b),this.options=a.extend({},a.fn.collapse.defaults,c),this.options.parent&&(this.$parent=a(this.options.parent)),this.options.toggle&&this.toggle()};b.prototype={constructor:b,dimension:function(){var a=this.$element.hasClass("width");return a?"width":"height"},show:function(){var b,c,d,e;if(this.transitioning)return;b=this.dimension(),c=a.camelCase(["scroll",b].join("-")),d=this.$parent&&this.$parent.find("> .accordion-group > .in");if(d&&d.length){e=d.data("collapse");if(e&&e.transitioning)return;d.collapse("hide"),e||d.data("collapse",null)}this.$element[b](0),this.transition("addClass",a.Event("show"),"shown"),this.$element[b](this.$element[0][c])},hide:function(){var b;if(this.transitioning)return;b=this.dimension(),this.reset(this.$element[b]()),this.transition("removeClass",a.Event("hide"),"hidden"),this.$element[b](0)},reset:function(a){var b=this.dimension();return this.$element.removeClass("collapse")[b](a||"auto")[0].offsetWidth,this.$element[a!==null?"addClass":"removeClass"]("collapse"),this},transition:function(b,c,d){var e=this,f=function(){c.type=="show"&&e.reset(),e.transitioning=0,e.$element.trigger(d)};this.$element.trigger(c);if(c.isDefaultPrevented())return;this.transitioning=1,this.$element[b]("in"),a.support.transition&&this.$element.hasClass("collapse")?this.$element.one(a.support.transition.end,f):f()},toggle:function(){this[this.$element.hasClass("in")?"hide":"show"]()}},a.fn.collapse=function(c){return this.each(function(){var d=a(this),e=d.data("collapse"),f=typeof c=="object"&&c;e||d.data("collapse",e=new b(this,f)),typeof c=="string"&&e[c]()})},a.fn.collapse.defaults={toggle:!0},a.fn.collapse.Constructor=b,a(function(){a("body").on("click.collapse.data-api","[data-toggle=collapse]",function(b){var c=a(this),d,e=c.attr("data-target")||b.preventDefault()||(d=c.attr("href"))&&d.replace(/.*(?=#[^\s]+$)/,""),f=a(e).data("collapse")?"toggle":c.data();a(e).collapse(f)})})}(window.jQuery),!function(a){function d(){a(b).parent().removeClass("open")}"use strict";var b='[data-toggle="dropdown"]',c=function(b){var c=a(b).on("click.dropdown.data-api",this.toggle);a("html").on("click.dropdown.data-api",function(){c.parent().removeClass("open")})};c.prototype={constructor:c,toggle:function(b){var c=a(this),e,f,g;if(c.is(".disabled, :disabled"))return;return f=c.attr("data-target"),f||(f=c.attr("href"),f=f&&f.replace(/.*(?=#[^\s]*$)/,"")),e=a(f),e.length||(e=c.parent()),g=e.hasClass("open"),d(),g||e.toggleClass("open"),!1}},a.fn.dropdown=function(b){return this.each(function(){var d=a(this),e=d.data("dropdown");e||d.data("dropdown",e=new c(this)),typeof b=="string"&&e[b].call(d)})},a.fn.dropdown.Constructor=c,a(function(){a("html").on("click.dropdown.data-api",d),a("body").on("click.dropdown",".dropdown form",function(a){a.stopPropagation()}).on("click.dropdown.data-api",b,c.prototype.toggle)})}(window.jQuery),!function(a){function c(){var b=this,c=setTimeout(function(){b.$element.off(a.support.transition.end),d.call(b)},500);this.$element.one(a.support.transition.end,function(){clearTimeout(c),d.call(b)})}function d(a){this.$element.hide().trigger("hidden"),e.call(this)}function e(b){var c=this,d=this.$element.hasClass("fade")?"fade":"";if(this.isShown&&this.options.backdrop){var e=a.support.transition&&d;this.$backdrop=a('<div class="modal-backdrop '+d+'" />').appendTo(document.body),this.options.backdrop!="static"&&this.$backdrop.click(a.proxy(this.hide,this)),e&&this.$backdrop[0].offsetWidth,this.$backdrop.addClass("in"),e?this.$backdrop.one(a.support.transition.end,b):b()}else!this.isShown&&this.$backdrop?(this.$backdrop.removeClass("in"),a.support.transition&&this.$element.hasClass("fade")?this.$backdrop.one(a.support.transition.end,a.proxy(f,this)):f.call(this)):b&&b()}function f(){this.$backdrop.remove(),this.$backdrop=null}function g(){var b=this;this.isShown&&this.options.keyboard?a(document).on("keyup.dismiss.modal",function(a){a.which==27&&b.hide()}):this.isShown||a(document).off("keyup.dismiss.modal")}"use strict";var b=function(b,c){this.options=c,this.$element=a(b).delegate('[data-dismiss="modal"]',"click.dismiss.modal",a.proxy(this.hide,this))};b.prototype={constructor:b,toggle:function(){return this[this.isShown?"hide":"show"]()},show:function(){var b=this,c=a.Event("show");this.$element.trigger(c);if(this.isShown||c.isDefaultPrevented())return;a("body").addClass("modal-open"),this.isShown=!0,g.call(this),e.call(this,function(){var c=a.support.transition&&b.$element.hasClass("fade");b.$element.parent().length||b.$element.appendTo(document.body),b.$element.show(),c&&b.$element[0].offsetWidth,b.$element.addClass("in"),c?b.$element.one(a.support.transition.end,function(){b.$element.trigger("shown")}):b.$element.trigger("shown")})},hide:function(b){b&&b.preventDefault();var e=this;b=a.Event("hide"),this.$element.trigger(b);if(!this.isShown||b.isDefaultPrevented())return;this.isShown=!1,a("body").removeClass("modal-open"),g.call(this),this.$element.removeClass("in"),a.support.transition&&this.$element.hasClass("fade")?c.call(this):d.call(this)}},a.fn.modal=function(c){return this.each(function(){var d=a(this),e=d.data("modal"),f=a.extend({},a.fn.modal.defaults,d.data(),typeof c=="object"&&c);e||d.data("modal",e=new b(this,f)),typeof c=="string"?e[c]():f.show&&e.show()})},a.fn.modal.defaults={backdrop:!0,keyboard:!0,show:!0},a.fn.modal.Constructor=b,a(function(){a("body").on("click.modal.data-api",'[data-toggle="modal"]',function(b){var c=a(this),d,e=a(c.attr("data-target")||(d=c.attr("href"))&&d.replace(/.*(?=#[^\s]+$)/,"")),f=e.data("modal")?"toggle":a.extend({},e.data(),c.data());b.preventDefault(),e.modal(f)})})}(window.jQuery),!function(a){"use strict";var b=function(a,b){this.init("tooltip",a,b)};b.prototype={constructor:b,init:function(b,c,d){var e,f;this.type=b,this.$element=a(c),this.options=this.getOptions(d),this.enabled=!0,this.options.trigger!="manual"&&(e=this.options.trigger=="hover"?"mouseenter":"focus",f=this.options.trigger=="hover"?"mouseleave":"blur",this.$element.on(e,this.options.selector,a.proxy(this.enter,this)),this.$element.on(f,this.options.selector,a.proxy(this.leave,this))),this.options.selector?this._options=a.extend({},this.options,{trigger:"manual",selector:""}):this.fixTitle()},getOptions:function(b){return b=a.extend({},a.fn[this.type].defaults,b,this.$element.data()),b.delay&&typeof b.delay=="number"&&(b.delay={show:b.delay,hide:b.delay}),b},enter:function(b){var c=a(b.currentTarget)[this.type](this._options).data(this.type);if(!c.options.delay||!c.options.delay.show)return c.show();clearTimeout(this.timeout),c.hoverState="in",this.timeout=setTimeout(function(){c.hoverState=="in"&&c.show()},c.options.delay.show)},leave:function(b){var c=a(b.currentTarget)[this.type](this._options).data(this.type);this.timeout&&clearTimeout(this.timeout);if(!c.options.delay||!c.options.delay.hide)return c.hide();c.hoverState="out",this.timeout=setTimeout(function(){c.hoverState=="out"&&c.hide()},c.options.delay.hide)},show:function(){var a,b,c,d,e,f,g;if(this.hasContent()&&this.enabled){a=this.tip(),this.setContent(),this.options.animation&&a.addClass("fade"),f=typeof this.options.placement=="function"?this.options.placement.call(this,a[0],this.$element[0]):this.options.placement,b=/in/.test(f),a.remove().css({top:0,left:0,display:"block"}).appendTo(b?this.$element:document.body),c=this.getPosition(b),d=a[0].offsetWidth,e=a[0].offsetHeight;switch(b?f.split(" ")[1]:f){case"bottom":g={top:c.top+c.height,left:c.left+c.width/2-d/2};break;case"top":g={top:c.top-e,left:c.left+c.width/2-d/2};break;case"left":g={top:c.top+c.height/2-e/2,left:c.left-d};break;case"right":g={top:c.top+c.height/2-e/2,left:c.left+c.width}}a.css(g).addClass(f).addClass("in")}},isHTML:function(a){return typeof a!="string"||a.charAt(0)==="<"&&a.charAt(a.length-1)===">"&&a.length>=3||/^(?:[^<]*<[\w\W]+>[^>]*$)/.exec(a)},setContent:function(){var a=this.tip(),b=this.getTitle();a.find(".tooltip-inner")[this.isHTML(b)?"html":"text"](b),a.removeClass("fade in top bottom left right")},hide:function(){function d(){var b=setTimeout(function(){c.off(a.support.transition.end).remove()},500);c.one(a.support.transition.end,function(){clearTimeout(b),c.remove()})}var b=this,c=this.tip();c.removeClass("in"),a.support.transition&&this.$tip.hasClass("fade")?d():c.remove()},fixTitle:function(){var a=this.$element;(a.attr("title")||typeof a.attr("data-original-title")!="string")&&a.attr("data-original-title",a.attr("title")||"").removeAttr("title")},hasContent:function(){return this.getTitle()},getPosition:function(b){return a.extend({},b?{top:0,left:0}:this.$element.offset(),{width:this.$element[0].offsetWidth,height:this.$element[0].offsetHeight})},getTitle:function(){var a,b=this.$element,c=this.options;return a=b.attr("data-original-title")||(typeof c.title=="function"?c.title.call(b[0]):c.title),a},tip:function(){return this.$tip=this.$tip||a(this.options.template)},validate:function(){this.$element[0].parentNode||(this.hide(),this.$element=null,this.options=null)},enable:function(){this.enabled=!0},disable:function(){this.enabled=!1},toggleEnabled:function(){this.enabled=!this.enabled},toggle:function(){this[this.tip().hasClass("in")?"hide":"show"]()}},a.fn.tooltip=function(c){return this.each(function(){var d=a(this),e=d.data("tooltip"),f=typeof c=="object"&&c;e||d.data("tooltip",e=new b(this,f)),typeof c=="string"&&e[c]()})},a.fn.tooltip.Constructor=b,a.fn.tooltip.defaults={animation:!0,placement:"top",selector:!1,template:'<div class="tooltip"><div class="tooltip-arrow"></div><div class="tooltip-inner"></div></div>',trigger:"hover",title:"",delay:0}}(window.jQuery),!function(a){"use strict";var b=function(a,b){this.init("popover",a,b)};b.prototype=a.extend({},a.fn.tooltip.Constructor.prototype,{constructor:b,setContent:function(){var a=this.tip(),b=this.getTitle(),c=this.getContent();a.find(".popover-title")[this.isHTML(b)?"html":"text"](b),a.find(".popover-content > *")[this.isHTML(c)?"html":"text"](c),a.removeClass("fade top bottom left right in")},hasContent:function(){return this.getTitle()||this.getContent()},getContent:function(){var a,b=this.$element,c=this.options;return a=b.attr("data-content")||(typeof c.content=="function"?c.content.call(b[0]):c.content),a},tip:function(){return this.$tip||(this.$tip=a(this.options.template)),this.$tip}}),a.fn.popover=function(c){return this.each(function(){var d=a(this),e=d.data("popover"),f=typeof c=="object"&&c;e||d.data("popover",e=new b(this,f)),typeof c=="string"&&e[c]()})},a.fn.popover.Constructor=b,a.fn.popover.defaults=a.extend({},a.fn.tooltip.defaults,{placement:"right",content:"",template:'<div class="popover"><div class="arrow"></div><div class="popover-inner"><h3 class="popover-title"></h3><div class="popover-content"><p></p></div></div></div>'})}(window.jQuery),!function(a){function b(b,c){var d=a.proxy(this.process,this),e=a(b).is("body")?a(window):a(b),f;this.options=a.extend({},a.fn.scrollspy.defaults,c),this.$scrollElement=e.on("scroll.scroll.data-api",d),this.selector=(this.options.target||(f=a(b).attr("href"))&&f.replace(/.*(?=#[^\s]+$)/,"")||"")+" .nav li > a",this.$body=a("body"),this.refresh(),this.process()}"use strict",b.prototype={constructor:b,refresh:function(){var b=this,c;this.offsets=a([]),this.targets=a([]),c=this.$body.find(this.selector).map(function(){var b=a(this),c=b.data("target")||b.attr("href"),d=/^#\w/.test(c)&&a(c);return d&&c.length&&[[d.position().top,c]]||null}).sort(function(a,b){return a[0]-b[0]}).each(function(){b.offsets.push(this[0]),b.targets.push(this[1])})},process:function(){var a=this.$scrollElement.scrollTop()+this.options.offset,b=this.$scrollElement[0].scrollHeight||this.$body[0].scrollHeight,c=b-this.$scrollElement.height(),d=this.offsets,e=this.targets,f=this.activeTarget,g;if(a>=c)return f!=(g=e.last()[0])&&this.activate(g);for(g=d.length;g--;)f!=e[g]&&a>=d[g]&&(!d[g+1]||a<=d[g+1])&&this.activate(e[g])},activate:function(b){var c,d;this.activeTarget=b,a(this.selector).parent(".active").removeClass("active"),d=this.selector+'[data-target="'+b+'"],'+this.selector+'[href="'+b+'"]',c=a(d).parent("li").addClass("active"),c.parent(".dropdown-menu")&&(c=c.closest("li.dropdown").addClass("active")),c.trigger("activate")}},a.fn.scrollspy=function(c){return this.each(function(){var d=a(this),e=d.data("scrollspy"),f=typeof c=="object"&&c;e||d.data("scrollspy",e=new b(this,f)),typeof c=="string"&&e[c]()})},a.fn.scrollspy.Constructor=b,a.fn.scrollspy.defaults={offset:10},a(function(){a('[data-spy="scroll"]').each(function(){var b=a(this);b.scrollspy(b.data())})})}(window.jQuery),!function(a){"use strict";var b=function(b){this.element=a(b)};b.prototype={constructor:b,show:function(){var b=this.element,c=b.closest("ul:not(.dropdown-menu)"),d=b.attr("data-target"),e,f,g;d||(d=b.attr("href"),d=d&&d.replace(/.*(?=#[^\s]*$)/,""));if(b.parent("li").hasClass("active"))return;e=c.find(".active a").last()[0],g=a.Event("show",{relatedTarget:e}),b.trigger(g);if(g.isDefaultPrevented())return;f=a(d),this.activate(b.parent("li"),c),this.activate(f,f.parent(),function(){b.trigger({type:"shown",relatedTarget:e})})},activate:function(b,c,d){function g(){e.removeClass("active").find("> .dropdown-menu > .active").removeClass("active"),b.addClass("active"),f?(b[0].offsetWidth,b.addClass("in")):b.removeClass("fade"),b.parent(".dropdown-menu")&&b.closest("li.dropdown").addClass("active"),d&&d()}var e=c.find("> .active"),f=d&&a.support.transition&&e.hasClass("fade");f?e.one(a.support.transition.end,g):g(),e.removeClass("in")}},a.fn.tab=function(c){return this.each(function(){var d=a(this),e=d.data("tab");e||d.data("tab",e=new b(this)),typeof c=="string"&&e[c]()})},a.fn.tab.Constructor=b,a(function(){a("body").on("click.tab.data-api",'[data-toggle="tab"], [data-toggle="pill"]',function(b){b.preventDefault(),a(this).tab("show")})})}(window.jQuery),!function(a){"use strict";var b=function(b,c){this.$element=a(b),this.options=a.extend({},a.fn.typeahead.defaults,c),this.matcher=this.options.matcher||this.matcher,this.sorter=this.options.sorter||this.sorter,this.highlighter=this.options.highlighter||this.highlighter,this.updater=this.options.updater||this.updater,this.$menu=a(this.options.menu).appendTo("body"),this.source=this.options.source,this.shown=!1,this.listen()};b.prototype={constructor:b,select:function(){var a=this.$menu.find(".active").attr("data-value");return this.$element.val(this.updater(a)).change(),this.hide()},updater:function(a){return a},show:function(){var b=a.extend({},this.$element.offset(),{height:this.$element[0].offsetHeight});return this.$menu.css({top:b.top+b.height,left:b.left}),this.$menu.show(),this.shown=!0,this},hide:function(){return this.$menu.hide(),this.shown=!1,this},lookup:function(b){var c=this,d,e;return this.query=this.$element.val(),this.query?(d=a.grep(this.source,function(a){return c.matcher(a)}),d=this.sorter(d),d.length?this.render(d.slice(0,this.options.items)).show():this.shown?this.hide():this):this.shown?this.hide():this},matcher:function(a){return~a.toLowerCase().indexOf(this.query.toLowerCase())},sorter:function(a){var b=[],c=[],d=[],e;while(e=a.shift())e.toLowerCase().indexOf(this.query.toLowerCase())?~e.indexOf(this.query)?c.push(e):d.push(e):b.push(e);return b.concat(c,d)},highlighter:function(a){var b=this.query.replace(/[\-\[\]{}()*+?.,\\\^$|#\s]/g,"\\$&");return a.replace(new RegExp("("+b+")","ig"),function(a,b){return"<strong>"+b+"</strong>"})},render:function(b){var c=this;return b=a(b).map(function(b,d){return b=a(c.options.item).attr("data-value",d),b.find("a").html(c.highlighter(d)),b[0]}),b.first().addClass("active"),this.$menu.html(b),this},next:function(b){var c=this.$menu.find(".active").removeClass("active"),d=c.next();d.length||(d=a(this.$menu.find("li")[0])),d.addClass("active")},prev:function(a){var b=this.$menu.find(".active").removeClass("active"),c=b.prev();c.length||(c=this.$menu.find("li").last()),c.addClass("active")},listen:function(){this.$element.on("blur",a.proxy(this.blur,this)).on("keypress",a.proxy(this.keypress,this)).on("keyup",a.proxy(this.keyup,this)),(a.browser.webkit||a.browser.msie)&&this.$element.on("keydown",a.proxy(this.keypress,this)),this.$menu.on("click",a.proxy(this.click,this)).on("mouseenter","li",a.proxy(this.mouseenter,this))},keyup:function(a){switch(a.keyCode){case 40:case 38:break;case 9:case 13:if(!this.shown)return;this.select();break;case 27:if(!this.shown)return;this.hide();break;default:this.lookup()}a.stopPropagation(),a.preventDefault()},keypress:function(a){if(!this.shown)return;switch(a.keyCode){case 9:case 13:case 27:a.preventDefault();break;case 38:if(a.type!="keydown")break;a.preventDefault(),this.prev();break;case 40:if(a.type!="keydown")break;a.preventDefault(),this.next()}a.stopPropagation()},blur:function(a){var b=this;setTimeout(function(){b.hide()},150)},click:function(a){a.stopPropagation(),a.preventDefault(),this.select()},mouseenter:function(b){this.$menu.find(".active").removeClass("active"),a(b.currentTarget).addClass("active")}},a.fn.typeahead=function(c){return this.each(function(){var d=a(this),e=d.data("typeahead"),f=typeof c=="object"&&c;e||d.data("typeahead",e=new b(this,f)),typeof c=="string"&&e[c]()})},a.fn.typeahead.defaults={source:[],items:8,menu:'<ul class="typeahead dropdown-menu"></ul>',item:'<li><a href="#"></a></li>'},a.fn.typeahead.Constructor=b,a(function(){a("body").on("focus.typeahead.data-api",'[data-provide="typeahead"]',function(b){var c=a(this);if(c.data("typeahead"))return;b.preventDefault(),c.typeahead(c.data())})})}(window.jQuery);
@@ -0,0 +1,205 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8">
5
+ <title>Formotion</title>
6
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
7
+ <meta name="description" content="">
8
+ <meta name="author" content="">
9
+
10
+ <!-- Le styles -->
11
+ <link href="assets/css/bootstrap.min.css" rel="stylesheet">
12
+ <style>
13
+ body {
14
+ padding-top: 60px; /* 60px to make the container go all the way to the bottom of the topbar */
15
+ }
16
+ </style>
17
+ <link href="assets/css/bootstrap-responsive.min.css" rel="stylesheet">
18
+ <link href="assets/css/pygments.css" rel="stylesheet">
19
+ <link href="assets/css/formotion.css" rel="stylesheet">
20
+
21
+ <!-- Le HTML5 shim, for IE6-8 support of HTML5 elements -->
22
+ <!--[if lt IE 9]>
23
+ <script src="http://html5shim.googlecode.com/svn/trunk/html5.js"></script>
24
+ <![endif]-->
25
+
26
+ <!-- Le fav and touch icons -->
27
+ </head>
28
+
29
+ <body>
30
+ <a id="fork-me" href="https://github.com/clayallsopp/formotion" target="_blank">Fork me on GitHub</a>
31
+
32
+ <div class="container">
33
+
34
+ <div class="hero-unit">
35
+ <h1><span><a href="http://github.com/clayallsopp/formotion" id="formotion" class="no-decoration">Formotion</a> </span><small>for RubyMotion</small></h1>
36
+ <p>Painless, productive views on iOS.</p>
37
+ <iframe src="http://ghbtns.com/github-btn.html?user=clayallsopp&repo=formotion&type=watch&count=true&size=large" allowtransparency="true" frameborder="0" scrolling="0" width="150px" height="30px"></iframe>
38
+ <iframe src="http://ghbtns.com/github-btn.html?user=clayallsopp&repo=formotion&type=fork&count=true&&size=large" allowtransparency="true" frameborder="0" scrolling="0" width="170px" height="30px"></iframe>
39
+ </div>
40
+
41
+ <div class="row">
42
+ <div class="span4">
43
+ <h2>Construct UI...</h2>
44
+ <img src="http://i.imgur.com/TMwXI.png" alt="Complex data form">
45
+ </div>
46
+
47
+ <div class="span8">
48
+ <h2>...With A Hash</h2>
49
+ <div class="highlight"><pre class='codehilite'><code class="ruby syntax"><span class="vi">@form</span> <span class="o">=</span> <span class="no">Formotion</span><span class="o">::</span><span class="no">Form</span><span class="o">.</span><span class="n">new</span><span class="p">({</span>
50
+ <span class="n">sections</span><span class="p">:</span> <span class="o">[</span><span class="p">{</span>
51
+ <span class="n">title</span><span class="p">:</span> <span class="s2">&quot;Register&quot;</span><span class="p">,</span>
52
+ <span class="n">rows</span><span class="p">:</span> <span class="o">[</span><span class="p">{</span>
53
+ <span class="n">title</span><span class="p">:</span> <span class="s2">&quot;Email&quot;</span><span class="p">,</span>
54
+ <span class="n">key</span><span class="p">:</span> <span class="ss">:email</span><span class="p">,</span>
55
+ <span class="n">placeholder</span><span class="p">:</span> <span class="s2">&quot;me@mail.com&quot;</span><span class="p">,</span>
56
+ <span class="n">type</span><span class="p">:</span> <span class="ss">:email</span><span class="p">,</span>
57
+ <span class="n">auto_correction</span><span class="p">:</span> <span class="ss">:no</span><span class="p">,</span>
58
+ <span class="n">auto_capitalization</span><span class="p">:</span> <span class="ss">:none</span>
59
+ <span class="p">},</span> <span class="p">{</span>
60
+ <span class="n">title</span><span class="p">:</span> <span class="s2">&quot;Password&quot;</span><span class="p">,</span>
61
+ <span class="n">key</span><span class="p">:</span> <span class="ss">:password</span><span class="p">,</span>
62
+ <span class="n">placeholder</span><span class="p">:</span> <span class="s2">&quot;required&quot;</span><span class="p">,</span>
63
+ <span class="n">type</span><span class="p">:</span> <span class="ss">:string</span><span class="p">,</span>
64
+ <span class="n">secure</span><span class="p">:</span> <span class="kp">true</span>
65
+ <span class="p">},</span> <span class="p">{</span>
66
+ <span class="n">title</span><span class="p">:</span> <span class="s2">&quot;Password&quot;</span><span class="p">,</span>
67
+ <span class="n">subtitle</span><span class="p">:</span> <span class="s2">&quot;Confirmation&quot;</span><span class="p">,</span>
68
+ <span class="n">key</span><span class="p">:</span> <span class="ss">:confirm</span><span class="p">,</span>
69
+ <span class="n">placeholder</span><span class="p">:</span> <span class="s2">&quot;required&quot;</span><span class="p">,</span>
70
+ <span class="n">type</span><span class="p">:</span> <span class="ss">:string</span><span class="p">,</span>
71
+ <span class="n">secure</span><span class="p">:</span> <span class="kp">true</span>
72
+ <span class="p">},</span> <span class="p">{</span>
73
+ <span class="n">title</span><span class="p">:</span> <span class="s2">&quot;Remember?&quot;</span><span class="p">,</span>
74
+ <span class="n">key</span><span class="p">:</span> <span class="ss">:remember</span><span class="p">,</span>
75
+ <span class="n">type</span><span class="p">:</span> <span class="ss">:switch</span><span class="p">,</span>
76
+ <span class="p">}</span><span class="o">]</span>
77
+ <span class="p">},</span> <span class="p">{</span>
78
+ <span class="n">title</span><span class="p">:</span> <span class="s2">&quot;Account Type&quot;</span><span class="p">,</span>
79
+ <span class="n">key</span><span class="p">:</span> <span class="ss">:account_type</span><span class="p">,</span>
80
+ <span class="n">select_one</span><span class="p">:</span> <span class="kp">true</span><span class="p">,</span>
81
+ <span class="n">rows</span><span class="p">:</span> <span class="o">[</span><span class="p">{</span>
82
+ <span class="n">title</span><span class="p">:</span> <span class="s2">&quot;Free&quot;</span><span class="p">,</span>
83
+ <span class="n">key</span><span class="p">:</span> <span class="ss">:free</span><span class="p">,</span>
84
+ <span class="n">type</span><span class="p">:</span> <span class="ss">:check</span><span class="p">,</span>
85
+ <span class="p">},</span> <span class="p">{</span>
86
+ <span class="n">title</span><span class="p">:</span> <span class="s2">&quot;Basic&quot;</span><span class="p">,</span>
87
+ <span class="n">value</span><span class="p">:</span> <span class="kp">true</span><span class="p">,</span>
88
+ <span class="n">key</span><span class="p">:</span> <span class="ss">:basic</span><span class="p">,</span>
89
+ <span class="n">type</span><span class="p">:</span> <span class="ss">:check</span><span class="p">,</span>
90
+ <span class="p">},</span> <span class="p">{</span>
91
+ <span class="n">title</span><span class="p">:</span> <span class="s2">&quot;Pro&quot;</span><span class="p">,</span>
92
+ <span class="n">key</span><span class="p">:</span> <span class="ss">:pro</span><span class="p">,</span>
93
+ <span class="n">type</span><span class="p">:</span> <span class="ss">:check</span><span class="p">,</span>
94
+ <span class="p">}</span><span class="o">]</span>
95
+ <span class="p">},</span> <span class="p">{</span>
96
+ <span class="n">rows</span><span class="p">:</span> <span class="o">[</span><span class="p">{</span>
97
+ <span class="n">title</span><span class="p">:</span> <span class="s2">&quot;Sign Up&quot;</span><span class="p">,</span>
98
+ <span class="n">type</span><span class="p">:</span> <span class="ss">:submit</span><span class="p">,</span>
99
+ <span class="p">}</span><span class="o">]</span>
100
+ <span class="p">}</span><span class="o">]</span>
101
+ <span class="p">})</span>
102
+
103
+ <span class="o">.</span><span class="n">.</span><span class="o">.</span>
104
+
105
+ <span class="vi">@form_controller</span> <span class="o">=</span> <span class="no">Formotion<span class="o">::</span>FormController</span><span class="o">.</span><span class="n">alloc</span><span class="o">.</span><span class="n">initWithForm</span><span class="p">(</span><span class="vi">@form</span><span class="p">)</span>
106
+ <span class="vi">@window</span><span class="o">.</span><span class="n">rootViewController</span> <span class="o">=</span> <span class="vi">@form_controller</span></code></pre>
107
+ </div>
108
+ </div>
109
+ </div>
110
+
111
+ <hr />
112
+
113
+ <div class="row">
114
+ <div class="span8">
115
+ <h2>Sync Models...</h2>
116
+ <div class="highlight"><pre id="syncmodels" class='codehilite'><code class="ruby syntax"><span class="k">class</span> <span class="nc">User</span>
117
+ <span class="kp">attr_accessor</span> <span class="ss">:name</span><span class="p">,</span> <span class="ss">:score</span><span class="p">,</span> <span class="ss">:team</span>
118
+
119
+ <span class="kp">include</span> <span class="no">Formotion</span><span class="o">::</span><span class="no">Formable</span>
120
+
121
+ <span class="n">form_property</span> <span class="ss">:name</span><span class="p">,</span> <span class="ss">:string</span>
122
+ <span class="n">form_property</span> <span class="ss">:score</span><span class="p">,</span> <span class="ss">:number</span><span class="p">,</span> <span class="n">transform</span><span class="p">:</span> <span class="nb">lambda</span> <span class="p">{</span> <span class="o">|</span><span class="n">value</span><span class="o">|</span> <span class="n">value</span><span class="o">.</span><span class="n">to_i</span> <span class="p">}</span>
123
+
124
+ <span class="n">form_property</span> <span class="ss">:team</span><span class="p">,</span> <span class="ss">:picker</span><span class="p">,</span> <span class="n">items</span><span class="p">:</span> <span class="o">[</span><span class="s2">&quot;Red&quot;</span><span class="p">,</span> <span class="s2">&quot;Blue&quot;</span><span class="p">,</span> <span class="s2">&quot;Green&quot;</span><span class="o">]</span>
125
+
126
+ <span class="n">form_title</span> <span class="s2">&quot;Edit User&quot;</span>
127
+ <span class="k">end</span>
128
+
129
+ <span class="o">.</span><span class="n">.</span><span class="o">.</span>
130
+
131
+ <span class="n">user</span> <span class="o">=</span> <span class="no">User</span><span class="o">.</span><span class="n">new</span><span class="p">(</span><span class="s2">&quot;Harry&quot;</span><span class="p">,</span> <span class="mi">100</span><span class="p">,</span> <span class="s2">&quot;Green&quot;</span><span class="p">)</span>
132
+ <span class="n">controller</span> <span class="o">=</span> <span class="no">Formotion</span><span class="o">::</span><span class="no">FormableController</span><span class="o">.</span><span class="n">alloc</span><span class="o">.</span><span class="n">initWithModel</span><span class="p">(</span><span class="n">user</span><span class="p">)</span>
133
+ <span class="nb">self</span><span class="o">.</span><span class="n">navigationController</span><span class="o">.</span><span class="n">pushViewController</span><span class="p">(</span><span class="n">controller</span><span class="p">,</span> <span class="n">animated</span><span class="ss">:true</span><span class="p">)</span>
134
+ </code></pre>
135
+ </div>
136
+ </div>
137
+
138
+ <div class="span4">
139
+ <h2>...To A View <small>(KVO)</small></h2>
140
+ <img src="https://a248.e.akamai.net/camo.github.com/18a55fe6aa9a33235867169a0a98d47ba5d02fe0/687474703a2f2f692e696d6775722e636f6d2f534b5133522e706e67" alt="Complex data form" id="sync-models">
141
+ </div>
142
+ </div>
143
+
144
+ <hr />
145
+
146
+ <div class="row" id="more-container">
147
+ <div class="span12">
148
+ <h2 id="andwaymore">And way more:</h2>
149
+ </div>
150
+ <div class="span4">
151
+ <h3>UI Elements</h3>
152
+ <img src="http://i.imgur.com/BKFIh.png">
153
+ <hr class="hidden-desktop" />
154
+ </div>
155
+ <div class="span4">
156
+ <h3>Subforms</h3>
157
+ <img src="http://i.imgur.com/Sfiq1.png" alt="Complex data form">
158
+ <hr class="hidden-desktop" />
159
+ </div>
160
+ <div class="span4">
161
+ <h3>Templates</h3>
162
+ <img src="http://i.imgur.com/kwG08.png" alt="Complex data form">
163
+ <hr class="hidden-desktop" />
164
+ </div>
165
+ </div>
166
+
167
+ <hr class="hidden-phone" />
168
+
169
+ <div class="row">
170
+ <div class="span12">
171
+ <div class="well">
172
+ <h1>Instructions <small><a href="http://github.com/clayallsopp/formotion" class="no-decoration">(full docs)</a></h1>
173
+ <hr />
174
+ <div class="row lead">
175
+ <div class="span3 install-instruct">
176
+ <h3>1. Install</h3>
177
+ <code>
178
+ gem install formotion
179
+ </code>
180
+ </div>
181
+ <div class="span3 install-instruct">
182
+ <h3>2. Rakefile</h3>
183
+ <code>
184
+ require 'formotion'
185
+ </code>
186
+ </div>
187
+ <div class="span5 install-instruct">
188
+ <h3>2. Use</h3>
189
+ <div class="highlight"><pre class='codehilite'><code class="ruby syntax"><span class="vi">@form</span> <span class="o">=</span> <span class="no">Formotion</span><span class="o">::</span><span class="no">Form</span><span class="o">.</span><span class="n">new</span><span class="p">({...})</span>
190
+
191
+ <span class="vi">@form_controller</span> <span class="o">=</span><br /> <span class="no">Formotion<span class="o">::</span>FormController</span><span class="o">.</span><span class="n">alloc</span><span class="o">.</span><span class="n">initWithForm</span><span class="p">(</span><span class="vi">@form</span><span class="p">)</span>
192
+ <span class="vi">@window</span><span class="o">.</span><span class="n">rootViewController</span> <span class="o">=</span> <span class="vi">@form_controller</span></code></pre>
193
+ </div>
194
+ </div>
195
+ </div>
196
+ </div>
197
+ </div>
198
+ </div>
199
+
200
+ <hr />
201
+
202
+
203
+ </div> <!-- /container -->
204
+ </body>
205
+ </html>
data/lib/formotion.rb CHANGED
@@ -3,13 +3,26 @@ require 'bubble-wrap/core'
3
3
  require 'bubble-wrap/camera'
4
4
 
5
5
  BW.require File.expand_path('../formotion/**/*.rb', __FILE__) do
6
- file("lib/formotion/row_type/string_row.rb").depends_on 'lib/formotion/row_type/base.rb'
6
+ base_row_type = 'lib/formotion/row_type/base.rb'
7
+
8
+ # hack to make sure base row type is compiled early
9
+ file('lib/formotion/base.rb').depends_on base_row_type
10
+
11
+ row_types = Dir.glob('lib/formotion/row_type/**/*.rb')
12
+ row_types.each {|file|
13
+ next if file == base_row_type
14
+ file(file).depends_on base_row_type
15
+ }
16
+
7
17
  ['date_row', 'email_row', 'number_row', 'phone_row'].each {|file|
8
18
  file("lib/formotion/row_type/#{file}.rb").depends_on 'lib/formotion/row_type/string_row.rb'
9
19
  }
20
+ ['submit_row', 'back_row'].each {|file|
21
+ file("lib/formotion/row_type/#{file}.rb").depends_on 'lib/formotion/row_type/button.rb'
22
+ }
10
23
 
11
24
  ['form/form.rb', 'row/row.rb', 'section/section.rb'].each {|file|
12
25
  file("lib/formotion/#{file}").depends_on 'lib/formotion/base.rb'
13
26
  }
14
- file("lib/formotion/form_controller.rb").depends_on 'lib/formotion/patch/ui_text_field.rb'
27
+ file("lib/formotion/controller/form_controller.rb").depends_on 'lib/formotion/patch/ui_text_field.rb'
15
28
  end
@@ -3,7 +3,7 @@ module Formotion
3
3
  def initialize(params = {})
4
4
  params.each { |key, value|
5
5
  if self.class.const_get(:PROPERTIES).member? key.to_sym
6
- self.send((key.to_s + "=:").to_sym, value)
6
+ self.send("#{key}=".to_sym, value)
7
7
  end
8
8
  }
9
9
  end
@@ -23,20 +23,20 @@ module Formotion
23
23
  encoder.encodeObject(self.send(prop), forKey: prop.to_s)
24
24
  }
25
25
  end
26
-
26
+
27
27
  def initWithCoder(decoder)
28
28
  self.init
29
29
  self.class.const_get(:SERIALIZE_PROPERTIES).each {|prop|
30
30
  value = decoder.decodeObjectForKey(prop.to_s)
31
- self.send((prop.to_s + "=:").to_sym, value) if not value.nil?
31
+ self.send("#{prop}=".to_sym, value) if not value.nil?
32
32
  }
33
33
  self
34
34
  end
35
-
35
+
36
36
  def copyWithZone(zone)
37
37
  copy = self.class.allocWithZone(zone).init
38
38
  self.class.const_get(:SERIALIZE_PROPERTIES).each {|prop|
39
- copy.send((prop.to_s + "=:").to_sym, self.send(prop))
39
+ copy.send("#{prop}=".to_sym, self.send(prop))
40
40
  }
41
41
  copy
42
42
  end
@@ -15,6 +15,8 @@ module Formotion
15
15
  def initWithForm(form)
16
16
  self.initWithStyle(UITableViewStyleGrouped)
17
17
  self.form = form
18
+ #self.view.setEditing true, animated: true
19
+ self.tableView.allowsSelectionDuringEditing = true
18
20
  self
19
21
  end
20
22
 
@@ -32,11 +34,9 @@ module Formotion
32
34
  def viewDidLoad
33
35
  super
34
36
 
35
- self.title = self.form.title
36
-
37
37
  # Triggers this block when the enter key is pressed
38
38
  # while editing the last text field.
39
- @form.sections[-1] && @form.sections[-1].rows[-1].on_enter do |row|
39
+ @form.sections[-1] && @form.sections[-1].rows && @form.sections[-1].rows[-1] && @form.sections[-1].rows[-1].on_enter do |row|
40
40
  if row.text_field
41
41
  @form.submit
42
42
  row.text_field.resignFirstResponder
@@ -48,5 +48,24 @@ module Formotion
48
48
  # and reloads the data.
49
49
  @form.controller = self
50
50
  end
51
+
52
+ # Subview Methods
53
+ def push_subform(form)
54
+ @subform_controller = self.class.alloc.initWithForm(form)
55
+
56
+ if self.navigationController
57
+ self.navigationController.pushViewController(@subform_controller, animated: true)
58
+ else
59
+ self.presentModalViewController(@subform_controller, animated: true)
60
+ end
61
+ end
62
+
63
+ def pop_subform
64
+ if self.navigationController
65
+ self.navigationController.popViewControllerAnimated true
66
+ else
67
+ self.dismissModalViewControllerAnimated true
68
+ end
69
+ end
51
70
  end
52
71
  end
@@ -0,0 +1,21 @@
1
+ module Formotion
2
+ class FormableController < Formotion::FormController
3
+ extend BW::KVO
4
+ include BW::KVO
5
+
6
+ attr_accessor :model
7
+
8
+ def initWithModel(model)
9
+ self.initWithForm(model.to_form)
10
+ self.model = model
11
+ self.form.sections.each { |section|
12
+ section.rows.each { |row|
13
+ observe(row, "value") do |old_value, new_value|
14
+ self.model.send("#{row.key}=", new_value)
15
+ end
16
+ }
17
+ }
18
+ self
19
+ end
20
+ end
21
+ end
@@ -3,7 +3,7 @@ module Formotion
3
3
  PROPERTIES = [
4
4
  # By default, Formotion::Controller will set it's title to this
5
5
  # (so navigation bars will reflect it).
6
- :title,
6
+ :title,
7
7
  # If you want to have some internal id to track the form.
8
8
  :id
9
9
  ]
@@ -87,7 +87,7 @@ module Formotion
87
87
 
88
88
  # Stores the callback block when you do #submit.
89
89
  # EX
90
- # @form.on_submit do
90
+ # @form.on_submit do
91
91
  # do_something(@form.render)
92
92
  # end
93
93
  #
@@ -103,13 +103,18 @@ module Formotion
103
103
  # Handles either zero or one arguments,
104
104
  # as shown above.
105
105
  def submit
106
+ if @on_submit_callback.nil?
107
+ p "`Form#submit` invoked without a callback, doing nothing."
108
+ return
109
+ end
110
+
106
111
  if @on_submit_callback.arity == 0
107
112
  @on_submit_callback.call
108
113
  elsif @on_submit_callback.arity == 1
109
114
  @on_submit_callback.call(self)
110
115
  end
111
116
  end
112
-
117
+
113
118
  #########################
114
119
  # Retreiving data
115
120
 
@@ -123,7 +128,7 @@ module Formotion
123
128
  # super handles all of the ::PROPERTIES
124
129
  h = super
125
130
  h[:sections] = self.sections.collect { |section|
126
- section.to_hash
131
+ section.to_hash
127
132
  }
128
133
  recursive_delete_nil(h)
129
134
  h
@@ -134,8 +139,8 @@ module Formotion
134
139
  # EX
135
140
  # @form = Formotion::Form.new(sections: [{
136
141
  # rows: [{
137
- # key: 'Email',
138
- # editable: true,
142
+ # key: 'Email',
143
+ # editable: true,
139
144
  # title: 'Email'
140
145
  # }]}])
141
146
  # ...user plays with the Form...
@@ -152,15 +157,33 @@ module Formotion
152
157
  }
153
158
  else
154
159
  section.rows.each {|row|
155
- next if row.submit_button?
156
- kv[row.key] = row.value
160
+ next if row.button?
161
+ if row.template_parent
162
+ # If this row is part of a template
163
+ # use the parent's key
164
+ kv[row.template_parent.key] ||= []
165
+ kv[row.template_parent.key] << row.value
166
+ else
167
+ kv[row.key] ||= row.value
168
+ end
157
169
  }
158
170
  end
159
171
  }
172
+ kv.merge! sub_render
160
173
  kv.delete_if {|k, v| k.nil? }
161
174
  kv
162
175
  end
163
176
 
177
+ def sub_render
178
+ kv = {}
179
+ rows = sections.map(&:rows).flatten
180
+ subform_rows = rows.select{ |row| row.subform != nil }
181
+ subform_rows.each do |subform_row|
182
+ kv[subform_row.key] = subform_row.subform.to_form.render
183
+ end
184
+ kv
185
+ end
186
+
164
187
  private
165
188
  def recursive_delete_nil(h)
166
189
  delete_empty = Proc.new { |k, v|
@@ -1,5 +1,5 @@
1
1
  module Formotion
2
- class Form
2
+ class Form < Formotion::Base
3
3
  attr_reader :table
4
4
  attr_reader :controller
5
5
  attr_reader :active_row
@@ -17,6 +17,7 @@ module Formotion
17
17
  # Table Methods
18
18
  def controller=(controller)
19
19
  @controller = controller
20
+ @controller.title = self.title
20
21
  self.table = controller.respond_to?(:table_view) ? controller.table_view : controller.tableView
21
22
  end
22
23
 
@@ -31,7 +32,7 @@ module Formotion
31
32
  def reload_data
32
33
  previous_row, next_row = nil
33
34
 
34
- last_row = self.sections[-1].rows[-1]
35
+ last_row = self.sections[-1] && self.sections[-1].rows[-1]
35
36
  if last_row
36
37
  last_row.return_key ||= UIReturnKeyDone
37
38
  end
@@ -69,14 +70,36 @@ module Formotion
69
70
 
70
71
  def tableView(tableView, heightForRowAtIndexPath: indexPath)
71
72
  row = row_for_index_path(indexPath)
72
- row.rowHeight || tableView.rowHeight
73
+ row.row_height || tableView.rowHeight
73
74
  end
74
75
 
76
+ def tableView(tableView, commitEditingStyle: editingStyle, forRowAtIndexPath: indexPath)
77
+ row = row_for_index_path(indexPath)
78
+ case editingStyle
79
+ when UITableViewCellEditingStyleInsert
80
+ row.object.on_insert(tableView, self)
81
+ when UITableViewCellEditingStyleDelete
82
+ row.object.on_delete(tableView, self)
83
+ end
84
+ end
85
+
86
+
75
87
  # UITableViewDelegate Methods
76
88
  def tableView(tableView, didSelectRowAtIndexPath:indexPath)
77
89
  tableView.deselectRowAtIndexPath(indexPath, animated:true)
78
90
  row = row_for_index_path(indexPath)
79
91
  row.object.on_select(tableView, self)
80
92
  end
93
+
94
+ def tableView(tableView, editingStyleForRowAtIndexPath:indexPath)
95
+ row = row_for_index_path(indexPath)
96
+ row.object.cellEditingStyle
97
+ end
98
+
99
+
100
+ def tableView(tableView, shouldIndentWhileEditingRowAtIndexPath: indexPath)
101
+ row = row_for_index_path(indexPath)
102
+ row.object.indentWhileEditing?
103
+ end
81
104
  end
82
105
  end