formotion 0.5.1 → 1.0

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