abyme 0.2.3 → 0.6.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (48) hide show
  1. checksums.yaml +4 -4
  2. data/.DS_Store +0 -0
  3. data/.babelrc +6 -0
  4. data/.github/workflows/build.yml +60 -0
  5. data/.gitignore +2 -0
  6. data/.simplecov +0 -0
  7. data/CHANGELOG.md +114 -0
  8. data/Gemfile.lock +182 -41
  9. data/README.md +120 -181
  10. data/Rakefile +31 -8
  11. data/abyme.gemspec +17 -1
  12. data/dist/abyme.js +2 -0
  13. data/dist/abyme.js.map +1 -0
  14. data/dist/abyme.modern.js +2 -0
  15. data/dist/abyme.modern.js.map +1 -0
  16. data/dist/abyme.module.js +2 -0
  17. data/dist/abyme.module.js.map +1 -0
  18. data/dist/abyme.umd.js +2 -0
  19. data/dist/abyme.umd.js.map +1 -0
  20. data/jest/jest-setup.js +2 -0
  21. data/lib/abyme.rb +2 -0
  22. data/lib/abyme/abyme_builder.rb +24 -15
  23. data/lib/abyme/action_view_extensions/builder.rb +17 -0
  24. data/lib/abyme/controller.rb +15 -0
  25. data/lib/abyme/engine.rb +3 -1
  26. data/lib/abyme/model.rb +81 -5
  27. data/lib/abyme/version.rb +4 -2
  28. data/lib/abyme/view_helpers.rb +213 -42
  29. data/lib/generators/abyme/controller/USAGE +16 -0
  30. data/lib/generators/abyme/controller/controller_generator.rb +25 -0
  31. data/lib/generators/abyme/model/USAGE +21 -0
  32. data/lib/generators/abyme/model/model_generator.rb +70 -0
  33. data/lib/generators/abyme/resource/USAGE +10 -0
  34. data/lib/generators/abyme/resource/resource_generator.rb +18 -0
  35. data/lib/generators/abyme/stimulus/USAGE +5 -0
  36. data/lib/generators/abyme/stimulus/stimulus_generator.rb +21 -0
  37. data/lib/generators/abyme/view/view_generator.rb +65 -0
  38. data/package-lock.json +31992 -0
  39. data/package.json +35 -5
  40. data/specs/index.test.js +26 -0
  41. data/{javascript → src}/abyme_controller.js +84 -11
  42. data/{javascript → src}/index.js +0 -0
  43. data/yarn.lock +8228 -24
  44. metadata +199 -9
  45. data/.travis.yml +0 -7
  46. data/lib/generators/.DS_Store +0 -0
  47. data/lib/generators/abyme/install_generator.rb +0 -25
  48. data/lib/generators/abyme/templates/abyme_controller.js +0 -17
data/abyme.gemspec CHANGED
@@ -28,7 +28,23 @@ Gem::Specification.new do |spec|
28
28
 
29
29
  spec.add_development_dependency "bundler", "~> 2.0"
30
30
  spec.add_development_dependency "rake", "~> 13.0"
31
+ # Tests
31
32
  spec.add_development_dependency "rspec-rails"
32
- spec.add_development_dependency "rails-dummy"
33
+ spec.add_development_dependency 'rails-controller-testing'
34
+ spec.add_development_dependency 'database_cleaner-active_record'
35
+ spec.add_development_dependency 'capybara'
36
+ spec.add_development_dependency 'webdrivers'
37
+ spec.add_development_dependency "generator_spec"
38
+
39
+ # Dummy app
33
40
  spec.add_development_dependency "sqlite3"
41
+ spec.add_development_dependency 'rails', "~> 6.0.3.6"
42
+ spec.add_development_dependency 'pry-byebug'
43
+ spec.add_development_dependency 'byebug'
44
+ spec.add_development_dependency 'web-console'
45
+ spec.add_development_dependency 'simple_form'
46
+
47
+ spec.add_development_dependency 'puma'
48
+ spec.add_development_dependency 'simplecov'
49
+ spec.add_development_dependency 'simplecov-lcov'
34
50
  end
data/dist/abyme.js ADDED
@@ -0,0 +1,2 @@
1
+ function e(t,n){return(e=Object.setPrototypeOf||function(e,t){return e.__proto__=t,e})(t,n)}function t(e,n,i){if(!e.s){if(i instanceof r){if(!i.s)return void(i.o=t.bind(null,e,n));1&n&&(n=i.s),i=i.v}if(i&&i.then)return void i.then(t.bind(null,e,n),t.bind(null,e,2));e.s=n,e.v=i;var o=e.o;o&&o(e)}}var n=function(n){var o,a;function s(){return n.apply(this,arguments)||this}a=n,(o=s).prototype=Object.create(a.prototype),o.prototype.constructor=o,e(o,a);var c,u=s.prototype;return u.connect=function(){console.log("Abyme Connected"),this.count&&this.add_default_associations()},u.add_association=function(e){if(e&&e.preventDefault(),this.element.dataset.limit&&this.limit_check())return this.create_event("limit-reached"),!1;var t=this.build_html();this.create_event("before-add"),this.associationsTarget.insertAdjacentHTML(this.position,t),this.create_event("after-add")},u.remove_association=function(e){e.preventDefault(),this.create_event("before-remove"),this.mark_for_destroy(e),this.create_event("after-remove")},u.create_event=function(e,t){void 0===t&&(t=null);var n=new CustomEvent("abyme:"+e,{detail:{controller:this,content:t}});this.element.dispatchEvent(n),this.dispatch(n,e)},u.dispatch=function(e,t){"before-add"===t&&this.abymeBeforeAdd&&this.abymeBeforeAdd(e),"after-add"===t&&this.abymeAfterAdd&&this.abymeAfterAdd(e),"before-remove"===t&&this.abymeBeforeRemove&&this.abymeBeforeAdd(e),"after-remove"===t&&this.abymeAfterRemove&&this.abymeAfterRemove(e)},u.abymeBeforeAdd=function(e){},u.abymeAfterAdd=function(e){},u.abymeBeforeRemove=function(e){},u.abymeAfterRemove=function(e){},u.build_html=function(){var e=this.templateTarget.innerHTML.replace(/NEW_RECORD/g,(new Date).getTime());if(e.match(/<template[\s\S]+<\/template>/)){var t=e.match(/<template[\s\S]+<\/template>/)[0].replace(/(\[\d{12,}\])(\[[^\[\]]+\]"){1}/g,"[NEW_RECORD]$2");e=e.replace(/<template[\s\S]+<\/template>/g,t)}return e},u.mark_for_destroy=function(e){var t=e.target.closest(".abyme--fields");t.querySelector("input[name*='_destroy']").value=1,t.style.display="none",t.classList.add("abyme--marked-for-destroy")},u.limit_check=function(){return this.newFieldsTargets.filter(function(e){return!e.classList.contains("abyme--marked-for-destroy")}).length>=parseInt(this.element.dataset.limit)},u.add_default_associations=function(){try{var e=this,n=0,o=function(e,n,o){for(var a;;){var s=e();if(i(s)&&(s=s.v),!s)return c;if(s.then){a=0;break}var c=o();if(c&&c.then){if(!i(c)){a=1;break}c=c.s}}var u=new r,f=t.bind(null,u,2);return(0===a?s.then(h):1===a?c.then(d):(void 0).then(function(){(s=e())?s.then?s.then(h).then(void 0,f):h(s):t(u,1,c)})).then(void 0,f),u;function d(n){c=n;do{if(!(s=e())||i(s)&&!s.v)return void t(u,1,c);if(s.then)return void s.then(h).then(void 0,f);i(c=o())&&(c=c.v)}while(!c||!c.then);c.then(d).then(void 0,f)}function h(e){e?(c=o())&&c.then?c.then(d).then(void 0,f):d(c):t(u,1,c)}}(function(){return n<e.count},0,function(){return e.add_association(),n++,Promise.resolve(e.sleep(1)).then(function(){})});return Promise.resolve(o&&o.then?o.then(function(){}):void 0)}catch(e){return Promise.reject(e)}},u.sleep=function(e){return new Promise(function(t){return setTimeout(t,e)})},(c=[{key:"count",get:function(){return this.element.dataset.minCount||0}},{key:"position",get:function(){return"end"===this.associationsTarget.dataset.abymePosition?"beforeend":"afterbegin"}}])&&function(e,t){for(var n=0;n<t.length;n++){var r=t[n];r.enumerable=r.enumerable||!1,r.configurable=!0,"value"in r&&(r.writable=!0),Object.defineProperty(e,r.key,r)}}(s.prototype,c),s}(require("stimulus").Controller),r=function(){function e(){}return e.prototype.then=function(n,r){var i=new e,o=this.s;if(o){var a=1&o?n:r;if(a){try{t(i,1,a(this.v))}catch(e){t(i,2,e)}return i}return this}return this.o=function(e){try{var o=e.v;1&e.s?t(i,1,n?n(o):o):r?t(i,1,r(o)):t(i,2,o)}catch(e){t(i,2,e)}},i},e}();function i(e){return e instanceof r&&1&e.s}n.targets=["template","associations","fields","newFields"],exports.AbymeController=n;
2
+ //# sourceMappingURL=abyme.js.map
data/dist/abyme.js.map ADDED
@@ -0,0 +1 @@
1
+ {"version":3,"file":"abyme.js","sources":["../src/abyme_controller.js"],"sourcesContent":["import { Controller } from 'stimulus';\n\nexport default class extends Controller {\n // static targets = ['template', 'associations', 'fields', 'newFields'];\n // Some applications don't compile correctly with the usual static syntax. \n // Thus implementing targets with standard getters below\n\n static targets = ['template', 'associations', 'fields', 'newFields']\n\n connect() {\n console.log(\"Abyme Connected\")\n\n if (this.count) {\n // If data-count is present,\n // add n default fields on page load\n\n this.add_default_associations();\n }\n }\n\n // return the value of the data-count attribute\n\n get count() {\n return this.element.dataset.minCount || 0;\n }\n \n // return the value of the data-position attribute\n // if there is no position specified set end as default\n\n get position() {\n return this.associationsTarget.dataset.abymePosition === 'end' ? 'beforeend' : 'afterbegin';\n }\n\n // ADD_ASSOCIATION\n\n // this function is call whenever a click occurs\n // on the element with the click->abyme#add_association\n // <button> element by default\n\n // if a data-count is present the add_association\n // will be call without an event so we have to check\n // this case\n\n // check for limit reached \n // dispatch an event if the limit is reached\n\n // - call the function build_html that take care\n // for building the correct html to be inserted in the DOM\n // - dispatch an event before insert\n // - insert html into the dom\n // - dispatch an event after insert\n\n add_association(event) {\n if (event) {\n event.preventDefault();\n }\n\n if (this.element.dataset.limit && this.limit_check()) {\n this.create_event('limit-reached')\n return false\n }\n\n const html = this.build_html();\n this.create_event('before-add');\n this.associationsTarget.insertAdjacentHTML(this.position, html);\n this.create_event('after-add');\n }\n\n // REMOVE_ASSOCIATION\n\n // this function is call whenever a click occurs\n // on the element with the click->abyme#remove_association\n // <button> element by default\n\n // - call the function mark_for_destroy that takes care\n // of marking the element for destruction and hiding it\n // - dispatch an event before mark & hide\n // - mark for descrution + hide the element\n // - dispatch an event after mark and hide\n\n remove_association(event) {\n event.preventDefault();\n\n this.create_event('before-remove');\n this.mark_for_destroy(event);\n this.create_event('after-remove');\n }\n\n // LIFECYCLE EVENTS RELATED\n\n // CREATE_EVENT\n\n // take a stage (String) => before-add, after-add...\n // create a new custom event \n // and dispatch at at the controller level\n\n create_event(stage, html = null) {\n const event = new CustomEvent(`abyme:${stage}`, { detail: {controller: this, content: html} });\n this.element.dispatchEvent(event);\n // WIP\n this.dispatch(event, stage);\n }\n\n // WIP : Trying to integrate event handling through controller inheritance\n dispatch(event, stage) {\n if (stage === 'before-add' && this.abymeBeforeAdd) this.abymeBeforeAdd(event);\n if (stage === 'after-add' && this.abymeAfterAdd) this.abymeAfterAdd(event);\n if (stage === 'before-remove' && this.abymeBeforeRemove) this.abymeBeforeAdd(event);\n if (stage === 'after-remove' && this.abymeAfterRemove) this.abymeAfterRemove(event);\n }\n\n abymeBeforeAdd(event) {\n }\n\n abymeAfterAdd(event) {\n }\n\n abymeBeforeRemove(event) {\n }\n\n abymeAfterRemove(event) {\n }\n\n // BUILD HTML\n\n // takes the html template and substitutes the sub-string\n // NEW_RECORD for a generated timestamp\n // then if there is a sub template in the html (multiple nested level)\n // set all the sub timestamps back as NEW_RECORD\n // finally returns the html \n\n build_html() {\n let html = this.templateTarget.innerHTML.replace(\n /NEW_RECORD/g,\n new Date().getTime()\n );\n \n if (html.match(/<template[\\s\\S]+<\\/template>/)) {\n const template = html\n .match(/<template[\\s\\S]+<\\/template>/)[0]\n .replace(/(\\[\\d{12,}\\])(\\[[^\\[\\]]+\\]\"){1}/g, `[NEW_RECORD]$2`);\n \n html = html.replace(/<template[\\s\\S]+<\\/template>/g, template);\n }\n\n return html;\n }\n \n // MARK_FOR_DESTROY\n\n // mark association for destruction\n // get the closest abyme--fields from the remove_association button\n // set the _destroy input value as 1\n // hide the element\n // add the class of abyme--marked-for-destroy to the element\n\n mark_for_destroy(event) {\n let item = event.target.closest('.abyme--fields');\n item.querySelector(\"input[name*='_destroy']\").value = 1;\n item.style.display = 'none';\n item.classList.add('abyme--marked-for-destroy')\n }\n\n\n // LIMIT_CHECK\n\n // Check if associations limit is reached\n // based on newFieldsTargets only\n // persisted fields are ignored\n\n limit_check() {\n return (this.newFieldsTargets\n .filter(item => !item.classList.contains('abyme--marked-for-destroy'))).length \n >= parseInt(this.element.dataset.limit)\n }\n\n // ADD_DEFAULT_ASSOCIATION\n\n // Add n default blank associations at page load\n // call sleep function to ensure uniqueness of timestamp\n\n async add_default_associations() {\n let i = 0\n while (i < this.count) {\n this.add_association()\n i++\n await this.sleep(1);\n }\n }\n\n sleep(ms) {\n return new Promise(resolve => setTimeout(resolve, ms));\n }\n}\n"],"names":["_settle","pact","state","value","s","_Pact","o","bind","v","then","observer","connect","console","log","this","count","add_default_associations","add_association","event","preventDefault","element","dataset","limit","limit_check","create_event","html","build_html","associationsTarget","insertAdjacentHTML","position","remove_association","mark_for_destroy","stage","CustomEvent","detail","controller","content","dispatchEvent","dispatch","abymeBeforeAdd","abymeAfterAdd","abymeBeforeRemove","abymeAfterRemove","templateTarget","innerHTML","replace","Date","getTime","match","template","item","target","closest","querySelector","style","display","classList","add","newFieldsTargets","filter","contains","length","parseInt","i","test","update","body","shouldContinue","_isSettledPact","result","reject","_resumeAfterTest","_resumeAfterBody","updateValue","_this2","sleep","ms","Promise","resolve","setTimeout","minCount","abymePosition","prototype","onFulfilled","onRejected","callback","e","_this","thenable","targets"],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ import{Controller as e}from"stimulus";class t extends e{connect(){console.log("Abyme Connected"),this.count&&this.add_default_associations()}get count(){return this.element.dataset.minCount||0}get position(){return"end"===this.associationsTarget.dataset.abymePosition?"beforeend":"afterbegin"}add_association(e){if(e&&e.preventDefault(),this.element.dataset.limit&&this.limit_check())return this.create_event("limit-reached"),!1;const t=this.build_html();this.create_event("before-add"),this.associationsTarget.insertAdjacentHTML(this.position,t),this.create_event("after-add")}remove_association(e){e.preventDefault(),this.create_event("before-remove"),this.mark_for_destroy(e),this.create_event("after-remove")}create_event(e,t=null){const a=new CustomEvent(`abyme:${e}`,{detail:{controller:this,content:t}});this.element.dispatchEvent(a),this.dispatch(a,e)}dispatch(e,t){"before-add"===t&&this.abymeBeforeAdd&&this.abymeBeforeAdd(e),"after-add"===t&&this.abymeAfterAdd&&this.abymeAfterAdd(e),"before-remove"===t&&this.abymeBeforeRemove&&this.abymeBeforeAdd(e),"after-remove"===t&&this.abymeAfterRemove&&this.abymeAfterRemove(e)}abymeBeforeAdd(e){}abymeAfterAdd(e){}abymeBeforeRemove(e){}abymeAfterRemove(e){}build_html(){let e=this.templateTarget.innerHTML.replace(/NEW_RECORD/g,(new Date).getTime());if(e.match(/<template[\s\S]+<\/template>/)){const t=e.match(/<template[\s\S]+<\/template>/)[0].replace(/(\[\d{12,}\])(\[[^\[\]]+\]"){1}/g,"[NEW_RECORD]$2");e=e.replace(/<template[\s\S]+<\/template>/g,t)}return e}mark_for_destroy(e){let t=e.target.closest(".abyme--fields");t.querySelector("input[name*='_destroy']").value=1,t.style.display="none",t.classList.add("abyme--marked-for-destroy")}limit_check(){return this.newFieldsTargets.filter(e=>!e.classList.contains("abyme--marked-for-destroy")).length>=parseInt(this.element.dataset.limit)}async add_default_associations(){let e=0;for(;e<this.count;)this.add_association(),e++,await this.sleep(1)}sleep(e){return new Promise(t=>setTimeout(t,e))}}t.targets=["template","associations","fields","newFields"];export{t as AbymeController};
2
+ //# sourceMappingURL=abyme.modern.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"abyme.modern.js","sources":["../src/abyme_controller.js"],"sourcesContent":["import { Controller } from 'stimulus';\n\nexport default class extends Controller {\n // static targets = ['template', 'associations', 'fields', 'newFields'];\n // Some applications don't compile correctly with the usual static syntax. \n // Thus implementing targets with standard getters below\n\n static targets = ['template', 'associations', 'fields', 'newFields']\n\n connect() {\n console.log(\"Abyme Connected\")\n\n if (this.count) {\n // If data-count is present,\n // add n default fields on page load\n\n this.add_default_associations();\n }\n }\n\n // return the value of the data-count attribute\n\n get count() {\n return this.element.dataset.minCount || 0;\n }\n \n // return the value of the data-position attribute\n // if there is no position specified set end as default\n\n get position() {\n return this.associationsTarget.dataset.abymePosition === 'end' ? 'beforeend' : 'afterbegin';\n }\n\n // ADD_ASSOCIATION\n\n // this function is call whenever a click occurs\n // on the element with the click->abyme#add_association\n // <button> element by default\n\n // if a data-count is present the add_association\n // will be call without an event so we have to check\n // this case\n\n // check for limit reached \n // dispatch an event if the limit is reached\n\n // - call the function build_html that take care\n // for building the correct html to be inserted in the DOM\n // - dispatch an event before insert\n // - insert html into the dom\n // - dispatch an event after insert\n\n add_association(event) {\n if (event) {\n event.preventDefault();\n }\n\n if (this.element.dataset.limit && this.limit_check()) {\n this.create_event('limit-reached')\n return false\n }\n\n const html = this.build_html();\n this.create_event('before-add');\n this.associationsTarget.insertAdjacentHTML(this.position, html);\n this.create_event('after-add');\n }\n\n // REMOVE_ASSOCIATION\n\n // this function is call whenever a click occurs\n // on the element with the click->abyme#remove_association\n // <button> element by default\n\n // - call the function mark_for_destroy that takes care\n // of marking the element for destruction and hiding it\n // - dispatch an event before mark & hide\n // - mark for descrution + hide the element\n // - dispatch an event after mark and hide\n\n remove_association(event) {\n event.preventDefault();\n\n this.create_event('before-remove');\n this.mark_for_destroy(event);\n this.create_event('after-remove');\n }\n\n // LIFECYCLE EVENTS RELATED\n\n // CREATE_EVENT\n\n // take a stage (String) => before-add, after-add...\n // create a new custom event \n // and dispatch at at the controller level\n\n create_event(stage, html = null) {\n const event = new CustomEvent(`abyme:${stage}`, { detail: {controller: this, content: html} });\n this.element.dispatchEvent(event);\n // WIP\n this.dispatch(event, stage);\n }\n\n // WIP : Trying to integrate event handling through controller inheritance\n dispatch(event, stage) {\n if (stage === 'before-add' && this.abymeBeforeAdd) this.abymeBeforeAdd(event);\n if (stage === 'after-add' && this.abymeAfterAdd) this.abymeAfterAdd(event);\n if (stage === 'before-remove' && this.abymeBeforeRemove) this.abymeBeforeAdd(event);\n if (stage === 'after-remove' && this.abymeAfterRemove) this.abymeAfterRemove(event);\n }\n\n abymeBeforeAdd(event) {\n }\n\n abymeAfterAdd(event) {\n }\n\n abymeBeforeRemove(event) {\n }\n\n abymeAfterRemove(event) {\n }\n\n // BUILD HTML\n\n // takes the html template and substitutes the sub-string\n // NEW_RECORD for a generated timestamp\n // then if there is a sub template in the html (multiple nested level)\n // set all the sub timestamps back as NEW_RECORD\n // finally returns the html \n\n build_html() {\n let html = this.templateTarget.innerHTML.replace(\n /NEW_RECORD/g,\n new Date().getTime()\n );\n \n if (html.match(/<template[\\s\\S]+<\\/template>/)) {\n const template = html\n .match(/<template[\\s\\S]+<\\/template>/)[0]\n .replace(/(\\[\\d{12,}\\])(\\[[^\\[\\]]+\\]\"){1}/g, `[NEW_RECORD]$2`);\n \n html = html.replace(/<template[\\s\\S]+<\\/template>/g, template);\n }\n\n return html;\n }\n \n // MARK_FOR_DESTROY\n\n // mark association for destruction\n // get the closest abyme--fields from the remove_association button\n // set the _destroy input value as 1\n // hide the element\n // add the class of abyme--marked-for-destroy to the element\n\n mark_for_destroy(event) {\n let item = event.target.closest('.abyme--fields');\n item.querySelector(\"input[name*='_destroy']\").value = 1;\n item.style.display = 'none';\n item.classList.add('abyme--marked-for-destroy')\n }\n\n\n // LIMIT_CHECK\n\n // Check if associations limit is reached\n // based on newFieldsTargets only\n // persisted fields are ignored\n\n limit_check() {\n return (this.newFieldsTargets\n .filter(item => !item.classList.contains('abyme--marked-for-destroy'))).length \n >= parseInt(this.element.dataset.limit)\n }\n\n // ADD_DEFAULT_ASSOCIATION\n\n // Add n default blank associations at page load\n // call sleep function to ensure uniqueness of timestamp\n\n async add_default_associations() {\n let i = 0\n while (i < this.count) {\n this.add_association()\n i++\n await this.sleep(1);\n }\n }\n\n sleep(ms) {\n return new Promise(resolve => setTimeout(resolve, ms));\n }\n}\n"],"names":["Controller","connect","console","log","this","count","add_default_associations","element","dataset","minCount","position","associationsTarget","abymePosition","add_association","event","preventDefault","limit","limit_check","create_event","html","build_html","insertAdjacentHTML","remove_association","mark_for_destroy","stage","CustomEvent","detail","controller","content","dispatchEvent","dispatch","abymeBeforeAdd","abymeAfterAdd","abymeBeforeRemove","abymeAfterRemove","templateTarget","innerHTML","replace","Date","getTime","match","template","item","target","closest","querySelector","value","style","display","classList","add","newFieldsTargets","filter","contains","length","parseInt","[object Object]","i","sleep","ms","Promise","resolve","setTimeout","targets"],"mappings":"sDAE6BA,EAO3BC,UACEC,QAAQC,IAAI,mBAERC,KAAKC,OAIPD,KAAKE,2BAMAD,YACP,YAAYE,QAAQC,QAAQC,UAAY,EAM9BC,eACV,MAAyD,aAA7CC,mBAAmBH,QAAQI,cAA0B,YAAc,aAsBjFC,gBAAgBC,GAKd,GAJIA,GACFA,EAAMC,iBAGJX,KAAKG,QAAQC,QAAQQ,OAASZ,KAAKa,cAErC,OADAb,KAAKc,aAAa,oBAIpB,MAAMC,EAAOf,KAAKgB,aAClBhB,KAAKc,aAAa,cAClBd,KAAKO,mBAAmBU,mBAAmBjB,KAAKM,SAAUS,GAC1Df,KAAKc,aAAa,aAepBI,mBAAmBR,GACjBA,EAAMC,iBAENX,KAAKc,aAAa,iBAClBd,KAAKmB,iBAAiBT,GACtBV,KAAKc,aAAa,gBAWpBA,aAAaM,EAAOL,EAAO,MACzB,MAAML,EAAQ,IAAIW,YAAa,SAAQD,IAAS,CAAEE,OAAQ,CAACC,WAAYvB,KAAMwB,QAAST,KACtFf,KAAKG,QAAQsB,cAAcf,GAE3BV,KAAK0B,SAAShB,EAAOU,GAIvBM,SAAShB,EAAOU,GACA,eAAVA,GAA0BpB,KAAK2B,gBAAgB3B,KAAK2B,eAAejB,GACzD,cAAVU,GAAyBpB,KAAK4B,eAAe5B,KAAK4B,cAAclB,GACtD,kBAAVU,GAA6BpB,KAAK6B,mBAAmB7B,KAAK2B,eAAejB,GAC/D,iBAAVU,GAA4BpB,KAAK8B,kBAAkB9B,KAAK8B,iBAAiBpB,GAG/EiB,eAAejB,IAGfkB,cAAclB,IAGdmB,kBAAkBnB,IAGlBoB,iBAAiBpB,IAWjBM,aACE,IAAID,EAAOf,KAAK+B,eAAeC,UAAUC,QACvC,eACA,IAAIC,MAAOC,WAGb,GAAIpB,EAAKqB,MAAM,gCAAiC,CAC9C,MAAMC,EAAWtB,EAChBqB,MAAM,gCAAgC,GACtCH,QAAQ,mCAAqC,kBAE9ClB,EAAOA,EAAKkB,QAAQ,gCAAiCI,GAGvD,OAAOtB,EAWTI,iBAAiBT,GACf,IAAI4B,EAAO5B,EAAM6B,OAAOC,QAAQ,kBAChCF,EAAKG,cAAc,2BAA2BC,MAAQ,EACtDJ,EAAKK,MAAMC,QAAU,OACrBN,EAAKO,UAAUC,IAAI,6BAUrBjC,cACE,YAAakC,iBACAC,OAAOV,IAASA,EAAKO,UAAUI,SAAS,8BAA+BC,QACrEC,SAASnD,KAAKG,QAAQC,QAAQQ,OAQjBwC,iCAC5B,IAAIC,EAAI,EACR,KAAOA,EAAIrD,KAAKC,OACdD,KAAKS,kBACL4C,eACWC,MAAM,GAIrBA,MAAMC,GACJ,WAAWC,QAAQC,GAAWC,WAAWD,EAASF,OAxL7CI,QAAU,CAAC,WAAY,eAAgB,SAAU"}
@@ -0,0 +1,2 @@
1
+ import{Controller as e}from"stimulus";function t(e,n){return(t=Object.setPrototypeOf||function(e,t){return e.__proto__=t,e})(e,n)}function n(e,t,r){if(!e.s){if(r instanceof i){if(!r.s)return void(r.o=n.bind(null,e,t));1&t&&(t=r.s),r=r.v}if(r&&r.then)return void r.then(n.bind(null,e,t),n.bind(null,e,2));e.s=t,e.v=r;var o=e.o;o&&o(e)}}var r=function(e){var r,a;function s(){return e.apply(this,arguments)||this}a=e,(r=s).prototype=Object.create(a.prototype),r.prototype.constructor=r,t(r,a);var c,f=s.prototype;return f.connect=function(){console.log("Abyme Connected"),this.count&&this.add_default_associations()},f.add_association=function(e){if(e&&e.preventDefault(),this.element.dataset.limit&&this.limit_check())return this.create_event("limit-reached"),!1;var t=this.build_html();this.create_event("before-add"),this.associationsTarget.insertAdjacentHTML(this.position,t),this.create_event("after-add")},f.remove_association=function(e){e.preventDefault(),this.create_event("before-remove"),this.mark_for_destroy(e),this.create_event("after-remove")},f.create_event=function(e,t){void 0===t&&(t=null);var n=new CustomEvent("abyme:"+e,{detail:{controller:this,content:t}});this.element.dispatchEvent(n),this.dispatch(n,e)},f.dispatch=function(e,t){"before-add"===t&&this.abymeBeforeAdd&&this.abymeBeforeAdd(e),"after-add"===t&&this.abymeAfterAdd&&this.abymeAfterAdd(e),"before-remove"===t&&this.abymeBeforeRemove&&this.abymeBeforeAdd(e),"after-remove"===t&&this.abymeAfterRemove&&this.abymeAfterRemove(e)},f.abymeBeforeAdd=function(e){},f.abymeAfterAdd=function(e){},f.abymeBeforeRemove=function(e){},f.abymeAfterRemove=function(e){},f.build_html=function(){var e=this.templateTarget.innerHTML.replace(/NEW_RECORD/g,(new Date).getTime());if(e.match(/<template[\s\S]+<\/template>/)){var t=e.match(/<template[\s\S]+<\/template>/)[0].replace(/(\[\d{12,}\])(\[[^\[\]]+\]"){1}/g,"[NEW_RECORD]$2");e=e.replace(/<template[\s\S]+<\/template>/g,t)}return e},f.mark_for_destroy=function(e){var t=e.target.closest(".abyme--fields");t.querySelector("input[name*='_destroy']").value=1,t.style.display="none",t.classList.add("abyme--marked-for-destroy")},f.limit_check=function(){return this.newFieldsTargets.filter(function(e){return!e.classList.contains("abyme--marked-for-destroy")}).length>=parseInt(this.element.dataset.limit)},f.add_default_associations=function(){try{var e=this,t=0,r=function(e,t,r){for(var a;;){var s=e();if(o(s)&&(s=s.v),!s)return c;if(s.then){a=0;break}var c=r();if(c&&c.then){if(!o(c)){a=1;break}c=c.s}}var f=new i,u=n.bind(null,f,2);return(0===a?s.then(h):1===a?c.then(d):(void 0).then(function(){(s=e())?s.then?s.then(h).then(void 0,u):h(s):n(f,1,c)})).then(void 0,u),f;function d(t){c=t;do{if(!(s=e())||o(s)&&!s.v)return void n(f,1,c);if(s.then)return void s.then(h).then(void 0,u);o(c=r())&&(c=c.v)}while(!c||!c.then);c.then(d).then(void 0,u)}function h(e){e?(c=r())&&c.then?c.then(d).then(void 0,u):d(c):n(f,1,c)}}(function(){return t<e.count},0,function(){return e.add_association(),t++,Promise.resolve(e.sleep(1)).then(function(){})});return Promise.resolve(r&&r.then?r.then(function(){}):void 0)}catch(e){return Promise.reject(e)}},f.sleep=function(e){return new Promise(function(t){return setTimeout(t,e)})},(c=[{key:"count",get:function(){return this.element.dataset.minCount||0}},{key:"position",get:function(){return"end"===this.associationsTarget.dataset.abymePosition?"beforeend":"afterbegin"}}])&&function(e,t){for(var n=0;n<t.length;n++){var r=t[n];r.enumerable=r.enumerable||!1,r.configurable=!0,"value"in r&&(r.writable=!0),Object.defineProperty(e,r.key,r)}}(s.prototype,c),s}(e),i=function(){function e(){}return e.prototype.then=function(t,r){var i=new e,o=this.s;if(o){var a=1&o?t:r;if(a){try{n(i,1,a(this.v))}catch(e){n(i,2,e)}return i}return this}return this.o=function(e){try{var o=e.v;1&e.s?n(i,1,t?t(o):o):r?n(i,1,r(o)):n(i,2,o)}catch(e){n(i,2,e)}},i},e}();function o(e){return e instanceof i&&1&e.s}r.targets=["template","associations","fields","newFields"];export{r as AbymeController};
2
+ //# sourceMappingURL=abyme.module.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"abyme.module.js","sources":["../src/abyme_controller.js"],"sourcesContent":["import { Controller } from 'stimulus';\n\nexport default class extends Controller {\n // static targets = ['template', 'associations', 'fields', 'newFields'];\n // Some applications don't compile correctly with the usual static syntax. \n // Thus implementing targets with standard getters below\n\n static targets = ['template', 'associations', 'fields', 'newFields']\n\n connect() {\n console.log(\"Abyme Connected\")\n\n if (this.count) {\n // If data-count is present,\n // add n default fields on page load\n\n this.add_default_associations();\n }\n }\n\n // return the value of the data-count attribute\n\n get count() {\n return this.element.dataset.minCount || 0;\n }\n \n // return the value of the data-position attribute\n // if there is no position specified set end as default\n\n get position() {\n return this.associationsTarget.dataset.abymePosition === 'end' ? 'beforeend' : 'afterbegin';\n }\n\n // ADD_ASSOCIATION\n\n // this function is call whenever a click occurs\n // on the element with the click->abyme#add_association\n // <button> element by default\n\n // if a data-count is present the add_association\n // will be call without an event so we have to check\n // this case\n\n // check for limit reached \n // dispatch an event if the limit is reached\n\n // - call the function build_html that take care\n // for building the correct html to be inserted in the DOM\n // - dispatch an event before insert\n // - insert html into the dom\n // - dispatch an event after insert\n\n add_association(event) {\n if (event) {\n event.preventDefault();\n }\n\n if (this.element.dataset.limit && this.limit_check()) {\n this.create_event('limit-reached')\n return false\n }\n\n const html = this.build_html();\n this.create_event('before-add');\n this.associationsTarget.insertAdjacentHTML(this.position, html);\n this.create_event('after-add');\n }\n\n // REMOVE_ASSOCIATION\n\n // this function is call whenever a click occurs\n // on the element with the click->abyme#remove_association\n // <button> element by default\n\n // - call the function mark_for_destroy that takes care\n // of marking the element for destruction and hiding it\n // - dispatch an event before mark & hide\n // - mark for descrution + hide the element\n // - dispatch an event after mark and hide\n\n remove_association(event) {\n event.preventDefault();\n\n this.create_event('before-remove');\n this.mark_for_destroy(event);\n this.create_event('after-remove');\n }\n\n // LIFECYCLE EVENTS RELATED\n\n // CREATE_EVENT\n\n // take a stage (String) => before-add, after-add...\n // create a new custom event \n // and dispatch at at the controller level\n\n create_event(stage, html = null) {\n const event = new CustomEvent(`abyme:${stage}`, { detail: {controller: this, content: html} });\n this.element.dispatchEvent(event);\n // WIP\n this.dispatch(event, stage);\n }\n\n // WIP : Trying to integrate event handling through controller inheritance\n dispatch(event, stage) {\n if (stage === 'before-add' && this.abymeBeforeAdd) this.abymeBeforeAdd(event);\n if (stage === 'after-add' && this.abymeAfterAdd) this.abymeAfterAdd(event);\n if (stage === 'before-remove' && this.abymeBeforeRemove) this.abymeBeforeAdd(event);\n if (stage === 'after-remove' && this.abymeAfterRemove) this.abymeAfterRemove(event);\n }\n\n abymeBeforeAdd(event) {\n }\n\n abymeAfterAdd(event) {\n }\n\n abymeBeforeRemove(event) {\n }\n\n abymeAfterRemove(event) {\n }\n\n // BUILD HTML\n\n // takes the html template and substitutes the sub-string\n // NEW_RECORD for a generated timestamp\n // then if there is a sub template in the html (multiple nested level)\n // set all the sub timestamps back as NEW_RECORD\n // finally returns the html \n\n build_html() {\n let html = this.templateTarget.innerHTML.replace(\n /NEW_RECORD/g,\n new Date().getTime()\n );\n \n if (html.match(/<template[\\s\\S]+<\\/template>/)) {\n const template = html\n .match(/<template[\\s\\S]+<\\/template>/)[0]\n .replace(/(\\[\\d{12,}\\])(\\[[^\\[\\]]+\\]\"){1}/g, `[NEW_RECORD]$2`);\n \n html = html.replace(/<template[\\s\\S]+<\\/template>/g, template);\n }\n\n return html;\n }\n \n // MARK_FOR_DESTROY\n\n // mark association for destruction\n // get the closest abyme--fields from the remove_association button\n // set the _destroy input value as 1\n // hide the element\n // add the class of abyme--marked-for-destroy to the element\n\n mark_for_destroy(event) {\n let item = event.target.closest('.abyme--fields');\n item.querySelector(\"input[name*='_destroy']\").value = 1;\n item.style.display = 'none';\n item.classList.add('abyme--marked-for-destroy')\n }\n\n\n // LIMIT_CHECK\n\n // Check if associations limit is reached\n // based on newFieldsTargets only\n // persisted fields are ignored\n\n limit_check() {\n return (this.newFieldsTargets\n .filter(item => !item.classList.contains('abyme--marked-for-destroy'))).length \n >= parseInt(this.element.dataset.limit)\n }\n\n // ADD_DEFAULT_ASSOCIATION\n\n // Add n default blank associations at page load\n // call sleep function to ensure uniqueness of timestamp\n\n async add_default_associations() {\n let i = 0\n while (i < this.count) {\n this.add_association()\n i++\n await this.sleep(1);\n }\n }\n\n sleep(ms) {\n return new Promise(resolve => setTimeout(resolve, ms));\n }\n}\n"],"names":["_settle","pact","state","value","s","_Pact","o","bind","v","then","observer","connect","console","log","this","count","add_default_associations","add_association","event","preventDefault","element","dataset","limit","limit_check","create_event","html","build_html","associationsTarget","insertAdjacentHTML","position","remove_association","mark_for_destroy","stage","CustomEvent","detail","controller","content","dispatchEvent","dispatch","abymeBeforeAdd","abymeAfterAdd","abymeBeforeRemove","abymeAfterRemove","templateTarget","innerHTML","replace","Date","getTime","match","template","item","target","closest","querySelector","style","display","classList","add","newFieldsTargets","filter","contains","length","parseInt","i","test","update","body","shouldContinue","_isSettledPact","result","reject","_resumeAfterTest","_resumeAfterBody","updateValue","_this2","sleep","ms","Promise","resolve","setTimeout","minCount","abymePosition","Controller","prototype","onFulfilled","onRejected","callback","e","_this","thenable","targets"],"mappings":"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"}
data/dist/abyme.umd.js ADDED
@@ -0,0 +1,2 @@
1
+ !function(e,t){"object"==typeof exports&&"undefined"!=typeof module?t(exports,require("stimulus")):"function"==typeof define&&define.amd?define(["exports","stimulus"],t):t((e||self).abyme={},e.Stimulus)}(this,function(e,t){function n(e,t){return(n=Object.setPrototypeOf||function(e,t){return e.__proto__=t,e})(e,t)}function i(e,t,n){if(!e.s){if(n instanceof o){if(!n.s)return void(n.o=i.bind(null,e,t));1&t&&(t=n.s),n=n.v}if(n&&n.then)return void n.then(i.bind(null,e,t),i.bind(null,e,2));e.s=t,e.v=n;var r=e.o;r&&r(e)}}var r=function(e){var t,r;function s(){return e.apply(this,arguments)||this}r=e,(t=s).prototype=Object.create(r.prototype),t.prototype.constructor=t,n(t,r);var f,u=s.prototype;return u.connect=function(){console.log("Abyme Connected"),this.count&&this.add_default_associations()},u.add_association=function(e){if(e&&e.preventDefault(),this.element.dataset.limit&&this.limit_check())return this.create_event("limit-reached"),!1;var t=this.build_html();this.create_event("before-add"),this.associationsTarget.insertAdjacentHTML(this.position,t),this.create_event("after-add")},u.remove_association=function(e){e.preventDefault(),this.create_event("before-remove"),this.mark_for_destroy(e),this.create_event("after-remove")},u.create_event=function(e,t){void 0===t&&(t=null);var n=new CustomEvent("abyme:"+e,{detail:{controller:this,content:t}});this.element.dispatchEvent(n),this.dispatch(n,e)},u.dispatch=function(e,t){"before-add"===t&&this.abymeBeforeAdd&&this.abymeBeforeAdd(e),"after-add"===t&&this.abymeAfterAdd&&this.abymeAfterAdd(e),"before-remove"===t&&this.abymeBeforeRemove&&this.abymeBeforeAdd(e),"after-remove"===t&&this.abymeAfterRemove&&this.abymeAfterRemove(e)},u.abymeBeforeAdd=function(e){},u.abymeAfterAdd=function(e){},u.abymeBeforeRemove=function(e){},u.abymeAfterRemove=function(e){},u.build_html=function(){var e=this.templateTarget.innerHTML.replace(/NEW_RECORD/g,(new Date).getTime());if(e.match(/<template[\s\S]+<\/template>/)){var t=e.match(/<template[\s\S]+<\/template>/)[0].replace(/(\[\d{12,}\])(\[[^\[\]]+\]"){1}/g,"[NEW_RECORD]$2");e=e.replace(/<template[\s\S]+<\/template>/g,t)}return e},u.mark_for_destroy=function(e){var t=e.target.closest(".abyme--fields");t.querySelector("input[name*='_destroy']").value=1,t.style.display="none",t.classList.add("abyme--marked-for-destroy")},u.limit_check=function(){return this.newFieldsTargets.filter(function(e){return!e.classList.contains("abyme--marked-for-destroy")}).length>=parseInt(this.element.dataset.limit)},u.add_default_associations=function(){try{var e=this,t=0,n=function(e,t,n){for(var r;;){var s=e();if(a(s)&&(s=s.v),!s)return f;if(s.then){r=0;break}var f=n();if(f&&f.then){if(!a(f)){r=1;break}f=f.s}}var u=new o,c=i.bind(null,u,2);return(0===r?s.then(l):1===r?f.then(d):(void 0).then(function(){(s=e())?s.then?s.then(l).then(void 0,c):l(s):i(u,1,f)})).then(void 0,c),u;function d(t){f=t;do{if(!(s=e())||a(s)&&!s.v)return void i(u,1,f);if(s.then)return void s.then(l).then(void 0,c);a(f=n())&&(f=f.v)}while(!f||!f.then);f.then(d).then(void 0,c)}function l(e){e?(f=n())&&f.then?f.then(d).then(void 0,c):d(f):i(u,1,f)}}(function(){return t<e.count},0,function(){return e.add_association(),t++,Promise.resolve(e.sleep(1)).then(function(){})});return Promise.resolve(n&&n.then?n.then(function(){}):void 0)}catch(e){return Promise.reject(e)}},u.sleep=function(e){return new Promise(function(t){return setTimeout(t,e)})},(f=[{key:"count",get:function(){return this.element.dataset.minCount||0}},{key:"position",get:function(){return"end"===this.associationsTarget.dataset.abymePosition?"beforeend":"afterbegin"}}])&&function(e,t){for(var n=0;n<t.length;n++){var i=t[n];i.enumerable=i.enumerable||!1,i.configurable=!0,"value"in i&&(i.writable=!0),Object.defineProperty(e,i.key,i)}}(s.prototype,f),s}(t.Controller),o=function(){function e(){}return e.prototype.then=function(t,n){var r=new e,o=this.s;if(o){var a=1&o?t:n;if(a){try{i(r,1,a(this.v))}catch(e){i(r,2,e)}return r}return this}return this.o=function(e){try{var o=e.v;1&e.s?i(r,1,t?t(o):o):n?i(r,1,n(o)):i(r,2,o)}catch(e){i(r,2,e)}},r},e}();function a(e){return e instanceof o&&1&e.s}r.targets=["template","associations","fields","newFields"],e.AbymeController=r});
2
+ //# sourceMappingURL=abyme.umd.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"abyme.umd.js","sources":["../src/abyme_controller.js"],"sourcesContent":["import { Controller } from 'stimulus';\n\nexport default class extends Controller {\n // static targets = ['template', 'associations', 'fields', 'newFields'];\n // Some applications don't compile correctly with the usual static syntax. \n // Thus implementing targets with standard getters below\n\n static targets = ['template', 'associations', 'fields', 'newFields']\n\n connect() {\n console.log(\"Abyme Connected\")\n\n if (this.count) {\n // If data-count is present,\n // add n default fields on page load\n\n this.add_default_associations();\n }\n }\n\n // return the value of the data-count attribute\n\n get count() {\n return this.element.dataset.minCount || 0;\n }\n \n // return the value of the data-position attribute\n // if there is no position specified set end as default\n\n get position() {\n return this.associationsTarget.dataset.abymePosition === 'end' ? 'beforeend' : 'afterbegin';\n }\n\n // ADD_ASSOCIATION\n\n // this function is call whenever a click occurs\n // on the element with the click->abyme#add_association\n // <button> element by default\n\n // if a data-count is present the add_association\n // will be call without an event so we have to check\n // this case\n\n // check for limit reached \n // dispatch an event if the limit is reached\n\n // - call the function build_html that take care\n // for building the correct html to be inserted in the DOM\n // - dispatch an event before insert\n // - insert html into the dom\n // - dispatch an event after insert\n\n add_association(event) {\n if (event) {\n event.preventDefault();\n }\n\n if (this.element.dataset.limit && this.limit_check()) {\n this.create_event('limit-reached')\n return false\n }\n\n const html = this.build_html();\n this.create_event('before-add');\n this.associationsTarget.insertAdjacentHTML(this.position, html);\n this.create_event('after-add');\n }\n\n // REMOVE_ASSOCIATION\n\n // this function is call whenever a click occurs\n // on the element with the click->abyme#remove_association\n // <button> element by default\n\n // - call the function mark_for_destroy that takes care\n // of marking the element for destruction and hiding it\n // - dispatch an event before mark & hide\n // - mark for descrution + hide the element\n // - dispatch an event after mark and hide\n\n remove_association(event) {\n event.preventDefault();\n\n this.create_event('before-remove');\n this.mark_for_destroy(event);\n this.create_event('after-remove');\n }\n\n // LIFECYCLE EVENTS RELATED\n\n // CREATE_EVENT\n\n // take a stage (String) => before-add, after-add...\n // create a new custom event \n // and dispatch at at the controller level\n\n create_event(stage, html = null) {\n const event = new CustomEvent(`abyme:${stage}`, { detail: {controller: this, content: html} });\n this.element.dispatchEvent(event);\n // WIP\n this.dispatch(event, stage);\n }\n\n // WIP : Trying to integrate event handling through controller inheritance\n dispatch(event, stage) {\n if (stage === 'before-add' && this.abymeBeforeAdd) this.abymeBeforeAdd(event);\n if (stage === 'after-add' && this.abymeAfterAdd) this.abymeAfterAdd(event);\n if (stage === 'before-remove' && this.abymeBeforeRemove) this.abymeBeforeAdd(event);\n if (stage === 'after-remove' && this.abymeAfterRemove) this.abymeAfterRemove(event);\n }\n\n abymeBeforeAdd(event) {\n }\n\n abymeAfterAdd(event) {\n }\n\n abymeBeforeRemove(event) {\n }\n\n abymeAfterRemove(event) {\n }\n\n // BUILD HTML\n\n // takes the html template and substitutes the sub-string\n // NEW_RECORD for a generated timestamp\n // then if there is a sub template in the html (multiple nested level)\n // set all the sub timestamps back as NEW_RECORD\n // finally returns the html \n\n build_html() {\n let html = this.templateTarget.innerHTML.replace(\n /NEW_RECORD/g,\n new Date().getTime()\n );\n \n if (html.match(/<template[\\s\\S]+<\\/template>/)) {\n const template = html\n .match(/<template[\\s\\S]+<\\/template>/)[0]\n .replace(/(\\[\\d{12,}\\])(\\[[^\\[\\]]+\\]\"){1}/g, `[NEW_RECORD]$2`);\n \n html = html.replace(/<template[\\s\\S]+<\\/template>/g, template);\n }\n\n return html;\n }\n \n // MARK_FOR_DESTROY\n\n // mark association for destruction\n // get the closest abyme--fields from the remove_association button\n // set the _destroy input value as 1\n // hide the element\n // add the class of abyme--marked-for-destroy to the element\n\n mark_for_destroy(event) {\n let item = event.target.closest('.abyme--fields');\n item.querySelector(\"input[name*='_destroy']\").value = 1;\n item.style.display = 'none';\n item.classList.add('abyme--marked-for-destroy')\n }\n\n\n // LIMIT_CHECK\n\n // Check if associations limit is reached\n // based on newFieldsTargets only\n // persisted fields are ignored\n\n limit_check() {\n return (this.newFieldsTargets\n .filter(item => !item.classList.contains('abyme--marked-for-destroy'))).length \n >= parseInt(this.element.dataset.limit)\n }\n\n // ADD_DEFAULT_ASSOCIATION\n\n // Add n default blank associations at page load\n // call sleep function to ensure uniqueness of timestamp\n\n async add_default_associations() {\n let i = 0\n while (i < this.count) {\n this.add_association()\n i++\n await this.sleep(1);\n }\n }\n\n sleep(ms) {\n return new Promise(resolve => setTimeout(resolve, ms));\n }\n}\n"],"names":["_settle","pact","state","value","s","_Pact","o","bind","v","then","observer","connect","console","log","this","count","add_default_associations","add_association","event","preventDefault","element","dataset","limit","limit_check","create_event","html","build_html","associationsTarget","insertAdjacentHTML","position","remove_association","mark_for_destroy","stage","CustomEvent","detail","controller","content","dispatchEvent","dispatch","abymeBeforeAdd","abymeAfterAdd","abymeBeforeRemove","abymeAfterRemove","templateTarget","innerHTML","replace","Date","getTime","match","template","item","target","closest","querySelector","style","display","classList","add","newFieldsTargets","filter","contains","length","parseInt","i","test","update","body","shouldContinue","_isSettledPact","result","reject","_resumeAfterTest","_resumeAfterBody","updateValue","_this2","sleep","ms","Promise","resolve","setTimeout","minCount","abymePosition","Controller","prototype","onFulfilled","onRejected","callback","e","_this","thenable","targets"],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ import '@babel/polyfill'
2
+ import 'mutationobserver-shim'
data/lib/abyme.rb CHANGED
@@ -1,6 +1,8 @@
1
1
  require "abyme/version"
2
2
  require 'abyme/view_helpers'
3
3
  require 'abyme/engine'
4
+ require 'abyme/controller'
5
+ require 'abyme/action_view_extensions/builder'
4
6
 
5
7
  module Abyme
6
8
  class Error < StandardError; end
@@ -2,31 +2,40 @@ module Abyme
2
2
  class AbymeBuilder < ActionView::Base
3
3
  include ActionView
4
4
 
5
- def initialize(association:, form:, lookup_context:, partial:, &block)
5
+ # If a block is given to the #abymize helper
6
+ # it will instanciate a new AbymeBuilder
7
+ # and pass to it the association name (Symbol)
8
+ # the form object, lookup_context optionaly a partial path
9
+ # then yield itself to the block
10
+
11
+ def initialize(association:, form:, context:, partial:, &block)
6
12
  @association = association
7
13
  @form = form
8
- @lookup_context = lookup_context
14
+ @context = context
15
+ @lookup_context = context.lookup_context
9
16
  @partial = partial
10
- yield(self) if block_given?
17
+ yield(self) if block
11
18
  end
12
-
19
+
20
+ # RECORDS
21
+
22
+ # calls the #persisted_records_for helper method
23
+ # passing association, form and options to it
24
+
13
25
  def records(options = {})
14
26
  persisted_records_for(@association, @form, options) do |fields_for_association|
15
- render_association_partial(fields_for_association, options)
27
+ render_association_partial(@association, fields_for_association, @partial, @context)
16
28
  end
17
29
  end
18
-
30
+
31
+ # NEW_RECORDS
32
+
33
+ # calls the #new_records_for helper method
34
+ # passing association, form and options to it
19
35
  def new_records(options = {}, &block)
20
36
  new_records_for(@association, @form, options) do |fields_for_association|
21
- render_association_partial(fields_for_association, options)
37
+ render_association_partial(@association, fields_for_association, @partial, @context)
22
38
  end
23
39
  end
24
-
25
- private
26
-
27
- def render_association_partial(fields, options)
28
- partial = @partial || options[:partial] || "abyme/#{@association.to_s.singularize}_fields"
29
- ActionController::Base.render(partial: partial, locals: { f: fields })
30
- end
31
40
  end
32
- end
41
+ end
@@ -0,0 +1,17 @@
1
+ module Abyme
2
+ module ActionViewExtensions
3
+ module Builder
4
+ def abyme_for(association, options = {}, &block)
5
+ @template.abyme_for(association, self, options, &block)
6
+ end
7
+
8
+ alias :abymize :abyme_for
9
+ end
10
+ end
11
+ end
12
+
13
+ module ActionView::Helpers
14
+ class FormBuilder
15
+ include Abyme::ActionViewExtensions::Builder
16
+ end
17
+ end
@@ -0,0 +1,15 @@
1
+ module Abyme
2
+ module Controller
3
+ def abyme_attributes
4
+ return [] if resource_class.nil?
5
+
6
+ resource_class.abyme_attributes
7
+ end
8
+
9
+ private
10
+
11
+ def resource_class
12
+ self.class.name.match(/(.*)(Controller)/)[1].singularize.safe_constantize
13
+ end
14
+ end
15
+ end
data/lib/abyme/engine.rb CHANGED
@@ -4,9 +4,11 @@ module Abyme
4
4
 
5
5
  config.after_initialize do
6
6
  ActiveSupport.on_load :action_view do
7
- # ActionView::Base.send :include, Abyme::ViewHelpers
8
7
  include Abyme::ViewHelpers
9
8
  end
9
+ ActiveSupport.on_load :action_controller do
10
+ include Abyme::Controller
11
+ end
10
12
  end
11
13
  end
12
14
  end
data/lib/abyme/model.rb CHANGED
@@ -1,11 +1,87 @@
1
1
  module Abyme
2
2
  module Model
3
- extend ActiveSupport::Concern
4
-
5
- class_methods do
6
- def abyme_for(association, options = {})
3
+ module ClassMethods
4
+ def abymize(association, permit: nil, reject: nil, **options)
7
5
  default_options = {reject_if: :all_blank, allow_destroy: true}
8
- accepts_nested_attributes_for association, default_options.merge(options)
6
+ nested_attributes_options = default_options.merge(options)
7
+ accepts_nested_attributes_for association, nested_attributes_options
8
+ # Save allow_destroy value for this model/association for later
9
+ save_destroy_option(association, nested_attributes_options[:allow_destroy])
10
+ Abyme::Model.permit_attributes(self.name, association, permit || reject, permit.present?) if permit.present? || reject.present?
11
+ end
12
+
13
+ alias :abyme_for :abymize
14
+
15
+ def abyme_attributes
16
+ Abyme::Model.instance_variable_get(:@permitted_attributes)[self.name]
17
+ end
18
+
19
+ private
20
+
21
+ def save_destroy_option(association, value)
22
+ Abyme::Model.instance_variable_get(:@allow_destroy)[self.name][association] = value
23
+ end
24
+ end
25
+
26
+ @permitted_attributes ||= {}
27
+ @allow_destroy ||= {}
28
+
29
+ attr_accessor :allow_destroy
30
+ attr_reader :permitted_attributes
31
+
32
+ def self.permit_attributes(class_name, association, attributes, permit)
33
+ @permitted_attributes[class_name]["#{association}_attributes".to_sym] = AttributesBuilder.new(class_name, association, attributes, permit)
34
+ .build_attributes
35
+ end
36
+
37
+ def self.included(klass)
38
+ @permitted_attributes[klass.name] ||= {}
39
+ @allow_destroy[klass.name] ||= {}
40
+ klass.extend ClassMethods
41
+ end
42
+
43
+ class AttributesBuilder
44
+ def initialize(model, association, attributes, permit = true)
45
+ @model = model
46
+ @association = association
47
+ @attributes_list = attributes
48
+ @permit = permit
49
+ @association_class = @association.to_s.classify.constantize
50
+ end
51
+
52
+ def build_attributes
53
+ nested_attributes = @association_class.abyme_attributes if @association_class.respond_to? :abyme_attributes
54
+ authorized_attributes = build_default_attributes
55
+ if @permit && @attributes_list == :all_attributes
56
+ authorized_attributes = build_all_attributes(authorized_attributes, nested_attributes)
57
+ elsif @permit
58
+ @attributes_list << nested_attributes unless (nested_attributes.blank? || @attributes_list.include?(nested_attributes))
59
+ authorized_attributes += @attributes_list
60
+ else
61
+ authorized_attributes = build_all_attributes(authorized_attributes, nested_attributes)
62
+ authorized_attributes -= @attributes_list
63
+ end
64
+ authorized_attributes
65
+ end
66
+
67
+ def destroy_allowed?
68
+ Abyme::Model.instance_variable_get(:@allow_destroy).dig(@model, @association)
69
+ end
70
+
71
+ def add_all_attributes
72
+ @association_class.column_names.map(&:to_sym).reject { |attr| [:id, :created_at, :updated_at].include?(attr) }
73
+ end
74
+
75
+ def build_all_attributes(authorized_attributes, nested_attributes)
76
+ authorized_attributes += add_all_attributes
77
+ authorized_attributes << nested_attributes unless (nested_attributes.blank? || authorized_attributes.include?(nested_attributes))
78
+ authorized_attributes
79
+ end
80
+
81
+ def build_default_attributes
82
+ attributes = [:id]
83
+ attributes << :_destroy if destroy_allowed?
84
+ attributes
9
85
  end
10
86
  end
11
87
  end
data/lib/abyme/version.rb CHANGED
@@ -1,9 +1,11 @@
1
+ # :nocov:
1
2
  module Abyme
2
3
  module VERSION
3
4
  MAJOR = 0
4
- MINOR = 2
5
- PATCH = 3
5
+ MINOR = 6
6
+ PATCH = 0
6
7
 
7
8
  STRING = [MAJOR, MINOR, PATCH].join(".")
8
9
  end
9
10
  end
11
+ # :nocov:
@@ -2,98 +2,254 @@ require_relative "abyme_builder"
2
2
 
3
3
  module Abyme
4
4
  module ViewHelpers
5
+ # ABYME_FOR
5
6
 
6
- def abymize(association, form, options = {}, &block)
7
- content_tag(:div, data: { controller: 'abyme', limit: options[:limit], min_count: options[:min_count] }, id: "abyme--#{association}") do
8
- if block_given?
9
- yield(Abyme::AbymeBuilder.new(
10
- association: association, form: form, lookup_context: self.lookup_context, partial: options[:partial]
7
+ # this helper will generate the top level wrapper markup
8
+ # with the bare minimum html attributes (data-controller="abyme")
9
+ # it takes the Symbolized name of the association (plural) and the form object
10
+ # then you can pass a hash of options (see exemple below)
11
+ # if no block given it will generate a default markup for
12
+ # #persisted_records_for, #new_records_for & #add_associated_record methods
13
+ # if a block is given it will instanciate a new AbymeBuilder and pass to it
14
+ # the name of the association, the form object and the lookup_context
15
+
16
+ # == Options
17
+
18
+ # - limit (Integer)
19
+ # you can set a limit for the new association fields to display
20
+
21
+ # - min_count (Integer)
22
+ # set the default number of blank fields to display
23
+
24
+ # - partial (String)
25
+ # to customize the partial path by default #abyme_for will expect
26
+ # a partial to bbe present in views/abyme
27
+
28
+ # - Exemple
29
+
30
+ # <%= abyme_for(:tasks, f, limit: 3) do |abyme| %>
31
+ # ...
32
+ # <% end %>
33
+
34
+ # will output this html
35
+
36
+ # <div data-controller="abyme" data-limit="3" id="abyme--tasks">
37
+ # ...
38
+ # </div>
39
+
40
+ def abyme_for(association, form, options = {}, &block)
41
+ content_tag(:div, data: {controller: "abyme", limit: options[:limit], min_count: options[:min_count]}, id: "abyme--#{association}") do
42
+ if block
43
+ yield(
44
+ Abyme::AbymeBuilder.new(
45
+ association: association, form: form, context: self, partial: options[:partial]
11
46
  )
12
47
  )
13
48
  else
14
- model = association.to_s.singularize.classify.constantize
49
+ # model = association.to_s.singularize.classify.constantize
50
+ model = association.to_s.singularize
15
51
  concat(persisted_records_for(association, form, options))
16
- concat(new_records_for(association, form, options))
17
- concat(add_association(content: options[:button_text] || "Add #{model}"))
52
+ concat(new_records_for(association, form, options))
53
+ concat(add_associated_record(content: options[:button_text] || "Add #{model}"))
18
54
  end
19
55
  end
20
56
  end
21
57
 
58
+ alias_method :abymize, :abyme_for
59
+
60
+ # NEW_RECORDS_FOR
61
+
62
+ # this helper is call by the AbymeBuilder #new_records instance method
63
+ # it generates the html markup for new associations fields
64
+ # it takes the association (Symbol) and the form object
65
+ # then a hash of options.
66
+
67
+ # - Exemple
68
+ # <%= abyme_for(:tasks, f) do |abyme| %>
69
+ # <%= abyme.new_records %>
70
+ # ...
71
+ # <% end %>
72
+
73
+ # will output this html
74
+
75
+ # <div data-target="abyme.associations" data-association="tasks" data-abyme-position="end">
76
+ # <template class="abyme--task_template" data-target="abyme.template">
77
+ # <div data-target="abyme.fields abyme.newFields" class="abyme--fields task-fields">
78
+ # ... partial html goes here
79
+ # </div>
80
+ # </template>
81
+ # ... new rendered fields goes here
82
+ # </div>
83
+
84
+ # == Options
85
+ # - position (:start, :end)
86
+ # allows you to specify whether new fields added dynamically
87
+ # should go at the top or at the bottom
88
+ # :end is the default value
89
+
90
+ # - partial (String)
91
+ # to customize the partial path by default #abyme_for will expect
92
+ # a partial to bbe present in views/abyme
93
+
94
+ # - fields_html (Hash)
95
+ # allows you to pass any html attributes to each fields wrapper
96
+
97
+ # - wrapper_html (Hash)
98
+ # allows you to pass any html attributes to the the html element
99
+ # wrapping all the fields
100
+
22
101
  def new_records_for(association, form, options = {}, &block)
23
102
  options[:wrapper_html] ||= {}
24
103
 
25
- wrapper_default = {
26
- data: {
27
- target: 'abyme.associations',
28
- association: association,
29
- abyme_position: options[:position] || :end
30
- }
104
+ wrapper_default = {
105
+ data: {
106
+ abyme_target: "associations",
107
+ association: association,
108
+ abyme_position: options[:position] || :end
109
+ }
31
110
  }
32
111
 
33
- fields_default = { data: { target: 'abyme.fields abyme.newFields' } }
112
+ fields_default = {data: {target: "abyme.fields abyme.newFields"}}
34
113
 
35
114
  content_tag(:div, build_attributes(wrapper_default, options[:wrapper_html])) do
36
- content_tag(:template, class: "abyme--#{association.to_s.singularize}_template", data: { target: 'abyme.template' }) do
37
- form.fields_for association, association.to_s.classify.constantize.new, child_index: 'NEW_RECORD' do |f|
115
+ content_tag(:template, class: "abyme--#{association.to_s.singularize}_template", data: {abyme_target: "template"}) do
116
+ form.fields_for association, association.to_s.classify.constantize.new, child_index: "NEW_RECORD" do |f|
38
117
  content_tag(:div, build_attributes(fields_default, basic_fields_markup(options[:fields_html], association))) do
39
118
  # Here, if a block is passed, we're passing the association fields to it, rather than the form itself
40
- block_given? ? yield(f) : render(options[:partial] || "abyme/#{association.to_s.singularize}_fields", f: f)
119
+ # block_given? ? yield(f) : render(options[:partial] || "abyme/#{association.to_s.singularize}_fields", f: f)
120
+ block ? yield(f) : render_association_partial(association, f, options[:partial])
41
121
  end
42
122
  end
43
123
  end
44
124
  end
45
125
  end
46
-
126
+
127
+ # PERSISTED_RECORDS_FOR
128
+
129
+ # this helper is call by the AbymeBuilder #records instance method
130
+ # it generates the html markup for persisted associations fields
131
+ # it takes the association (Symbol) and the form object
132
+ # then a hash of options.
133
+
134
+ # - Exemple
135
+ # <%= abyme_for(:tasks, f) do |abyme| %>
136
+ # <%= abyme.records %>
137
+ # ...
138
+ # <% end %>
139
+
140
+ # will output this html
141
+
142
+ # <div>
143
+ # <div data-target="abyme.fields" class="abyme--fields task-fields">
144
+ # ... partial html goes here
145
+ # </div>
146
+ # </div>
147
+
148
+ # == Options
149
+ # - collection (Active Record Collection)
150
+ # allows you to pass an AR collection
151
+ # by default every associated records will be present
152
+
153
+ # - order (Hash)
154
+ # allows you to order the collection
155
+ # ex: order: { created_at: :desc }
156
+
157
+ # - partial (String)
158
+ # to customize the partial path by default #abyme_for will expect
159
+ # a partial to bbe present in views/abyme
160
+
161
+ # - fields_html (Hash)
162
+ # allows you to pass any html attributes to each fields wrapper
163
+
164
+ # - wrapper_html (Hash)
165
+ # allows you to pass any html attributes to the the html element
166
+ # wrapping all the fields
167
+
47
168
  def persisted_records_for(association, form, options = {})
48
169
  records = options[:collection] || form.object.send(association)
170
+ # return if records.empty?
171
+
49
172
  options[:wrapper_html] ||= {}
50
- fields_default = { data: { target: 'abyme.fields' } }
51
-
173
+ fields_default = {data: {abyme_target: "fields"}}
174
+
52
175
  if options[:order].present?
53
176
  records = records.order(options[:order])
54
- # Get invalid records
177
+ # by calling the order method on the AR collection
178
+ # we get rid of the records with errors
179
+ # so we have to get them back with the 2 lines below
55
180
  invalids = form.object.send(association).reject(&:persisted?)
56
181
  records = records.to_a.concat(invalids) if invalids.any?
57
- end
58
-
182
+ end
183
+
59
184
  content_tag(:div, options[:wrapper_html]) do
60
185
  form.fields_for(association, records) do |f|
61
186
  content_tag(:div, build_attributes(fields_default, basic_fields_markup(options[:fields_html], association))) do
62
- block_given? ? yield(f) : render(options[:partial] || "abyme/#{association.to_s.singularize}_fields", f: f)
187
+ block_given? ? yield(f) : render_association_partial(association, f, options[:partial])
63
188
  end
64
189
  end
65
190
  end
66
191
  end
67
-
68
- def add_association(options = {}, &block)
69
- action = 'click->abyme#add_association'
192
+
193
+ # ADD & REMOVE ASSOCIATION
194
+
195
+ # these helpers will call the #create_button method
196
+ # to generate the buttons for add and remove associations
197
+ # with the right action and a default content text for each button
198
+
199
+ def add_associated_record(options = {}, &block)
200
+ action = "click->abyme#add_association"
201
+ options[:content] ||= "Add Association"
70
202
  create_button(action, options, &block)
71
203
  end
72
-
73
- def remove_association(options = {}, &block)
74
- action = 'click->abyme#remove_association'
204
+
205
+ def remove_associated_record(options = {}, &block)
206
+ action = "click->abyme#remove_association"
207
+ options[:content] ||= "Remove Association"
75
208
  create_button(action, options, &block)
76
209
  end
77
210
 
211
+ alias_method :add_association, :add_associated_record
212
+ alias_method :remove_association, :remove_associated_record
213
+
78
214
  private
79
-
215
+
216
+ # CREATE_BUTTON
217
+
218
+ # this helper is call by either add_associated_record or remove_associated_record
219
+ # by default it will generate a button tag.
220
+
221
+ # == Options
222
+ # - content (String)
223
+ # allows you to set the button text
224
+
225
+ # - tag (Symbol)
226
+ # allows you to set the html tag of your choosing
227
+ # default if :button
228
+
229
+ # - html (Hash)
230
+ # to pass any html attributes you want.
231
+
80
232
  def create_button(action, options, &block)
81
233
  options[:html] ||= {}
82
234
  options[:tag] ||= :button
83
- options[:content] ||= 'Add Association'
84
-
85
- if block_given?
86
- content_tag(options[:tag], { data: { action: action } }.merge(options[:html])) do
235
+
236
+ if block
237
+ content_tag(options[:tag], {data: {action: action}}.merge(options[:html])) do
87
238
  capture(&block)
88
239
  end
89
240
  else
90
- content_tag(options[:tag], options[:content], { data: { action: action } }.merge(options[:html]))
241
+ content_tag(options[:tag], options[:content], {data: {action: action}}.merge(options[:html]))
91
242
  end
92
243
  end
93
244
 
245
+ # BASIC_FIELDS_MARKUP
246
+
247
+ # generates the default html classes for fields
248
+ # add optional classes if present
249
+
94
250
  def basic_fields_markup(html, association = nil)
95
251
  if html && html[:class]
96
- html[:class] = "abyme--fields #{association.to_s.singularize}-fields #{html[:class]}"
252
+ html[:class] = "abyme--fields #{association.to_s.singularize}-fields #{html[:class]}"
97
253
  else
98
254
  html ||= {}
99
255
  html[:class] = "abyme--fields #{association.to_s.singularize}-fields"
@@ -101,17 +257,32 @@ module Abyme
101
257
  html
102
258
  end
103
259
 
260
+ # BUILD_ATTRIBUTES
261
+
262
+ # add optionals html attributes without overwritting
263
+ # the default or already present ones
264
+
104
265
  def build_attributes(default, attr)
105
- # ADD NEW DATA ATTRIBUTES VALUES TO THE DEFAULT ONES (ONLY VALUES)
266
+ # Add new data attributes values to the default ones (only values)
106
267
  if attr[:data]
107
268
  default[:data].each do |key, value|
108
269
  default[:data][key] = "#{value} #{attr[:data][key]}".strip
109
270
  end
110
- # ADD NEW DATA ATTRIBUTES (KEYS & VALUES)
271
+ # Add new data attributes (keys & values)
111
272
  default[:data] = default[:data].merge(attr[:data].reject { |key, _| default[:data][key] })
112
273
  end
113
- # MERGE THE DATA ATTRIBUTES TO THE HASH OF HTML ATTRIBUTES
274
+ # Merge data attributes to the hash of html attributes
114
275
  default.merge(attr.reject { |key, _| key == :data })
115
276
  end
277
+
278
+ # RENDER PARTIAL
279
+
280
+ # renders a partial based on the passed path, or will expect a partial to be found in the views/abyme directory.
281
+
282
+ def render_association_partial(association, form, partial = nil, context = nil)
283
+ partial_path = partial || "abyme/#{association.to_s.singularize}_fields"
284
+ context ||= self
285
+ context.render(partial: partial_path, locals: {f: form})
286
+ end
116
287
  end
117
- end
288
+ end