Upgrade to Pro — share decks privately, control downloads, hide ads and more …

So, you wanna build an add-on...

So, you wanna build an add-on...

An introduction to building an ExpressionEngine Add-on.

Lodewijk Schutte

October 17, 2012
Tweet

More Decks by Lodewijk Schutte

Other Decks in Technology

Transcript

  1. 1. Analyze the Brief 2. Create Scaffold 3. Fill in

    the Gaps 4. Optimize & Simplify 5. Document it The Plan
  2. A member can like and unlike an entry » many-to-many

    member-entry relationship: DB table » Add/remove records to/from this table » A form per entry Show number of likes for each entry » Get like-count for each entry Show liked entries for a logged in member » Show channel entries based on likes by member Show entries with most likes » Show channel entries sorted by number of likes
  3. Overview of recently liked entries » List of entry +

    member + date » DB table needs a timestamp Overview of members that liked an entry » For a single entry, show members that liked it Overview of entries liked by a member » For a single member, show entries that he/she liked Keep like-counts accurate » When an entry is deleted, remove its likes » When a member is deleted, remove his/her likes
  4. - Define features - Think of a name - Sketch

    out database tables, if needed 1. Analyze the Brief
  5. - Define features - Think of a name - Sketch

    out database tables, if needed - Create dummy template code 1. Analyze the Brief
  6. {exp:channel:entries/channel="products"} / <h2>{title}</h2> / {exp:low_likes:show/entry_id="{entry_id}"} / / <p> / /

    / {total_likes}/Like{if/total_likes/!=/1}s{/if} / / </p> / {/exp:low_likes:show} {/exp:channel:entries}
  7. {exp:channel:entries/channel="products"} / <h2>{title}</h2> / {exp:low_likes:show/entry_id="{entry_id}"/form="yes"} / / <p> / /

    / {total_likes}/Like{if/total_likes/!=/1}s{/if} / / / {if/has_form} / / / / <button/type="submit"> / / / / / {if/is_liked}Unlike{if:else}Like{/if} / / / / </button> / / / {/if} / / </p> / {/exp:low_likes:show} {/exp:channel:entries}
  8. Classes & Methods » low_likes_upd install() uninstall() update() » low_likes

    show() entries() popular() toggle_like() » low_likes_mcp index() entries() members() » low_likes_ext member_delete() delete_entries_loop() channel_entries_query_result()
  9. - Keep the EE & CI docs ready - Outline

    methods with comments 3. Fill in the Gaps
  10. - Keep the EE & CI docs ready - Outline

    methods with comments - Create and test install/uninstall routine 3. Fill in the Gaps
  11. ext.low_likes.php » Add property to move (un)installation to module public/$required_by/=/array('module');

    upd.low_likes.php » Install method Create/new/table Add/row/to/exp_modules/table Add/row/to/exp_actions/table Add/rows/to/exp_extensions/table » Uninstall method Remove/rows/from/exp_modules,/exp_actions,/ exp_extensions/and/exp_module_member_groups/tables Drop/table » Update method Nothing/yet...
  12. - Keep the EE & CI docs ready - Outline

    methods with comments - Create and test install/uninstall routine - Create the rest 3. Fill in the Gaps
  13. How to draw an owl Step 1: draw some circles

    Step 2: draw the rest of the owl
  14. {exp:channel:entries/channel="products"} / <h2>{title}</h2> / {exp:low_likes:show/entry_id="{entry_id}"/form="yes"} / / <p> / /

    / {total_likes}/Like{if/total_likes/!=/1}s{/if} / / / {if/has_form} / / / / <button/type="submit"> / / / / / {if/is_liked}Unlike{if:else}Like{/if} / / / / </button> / / / {/if} / / </p> / {/exp:low_likes:show} {/exp:channel:entries}
  15. /** /*/Show/Likes/for/entry,/possibly/wrapped/in/a/form/tag /*/ public/function/show() { ///////Get/the/tagdata ////$tagdata/=/$thisE>EEE>TMPLE>tagdata; ///////Check/for/entry/ID/parameter,/bail/out/if/not/present ////if/(/!/($entry_id/=/$thisE>EEE>TMPLE> ////////////fetch_param('entry_id')))

    ////{ ///////////Add/message/to/template/log ////////$thisE>EEE>TMPLE>log_item('Low/Likes:/no/entry_id'); ////////return/$tagdata; ////} ///////Query/database/for/this/entry's/likes ///////Create/variables/array /
  16. ///////Query/database/for/this/entry's/likes ////$likes/=/array(); ////$query/=/$thisE>EEE>dbE>select('member_id') ///////////E>from('low_likes') ///////////E>where('entry_id',/$entry_id) ///////////E>get(); ////foreach/($queryE>result()/AS/$row) ////{ ////////$likes[]/=/$rowE>member_id; ////}/

    ///////Create/variables/array ////$member_id/=/$thisE>EEE>sessionE>userdata('member_id'); ////$form/=/(($thisE>EEE>TMPLE>fetch_param('form')/==/'yes') //////////////&&/$member_id); ////$vars/=/array( ////////'total_likes'/=>/count($likes), ////////'is_liked'////=>/in_array($member_id,/$likes),
  17. ///////Wrap/tagdata/in/a/form/if/needed ////if/($form) ////{ ///////////Initiate/data/array/for/form/creation ////////$fd/=/array( ////////////'id'////=>/$thisE>EEE>TMPLE>fetch_param('form_id'), ////////////'class'/=>/$thisE>EEE>TMPLE>fetch_param('form_class') ////////); ///////////Define/default/hidden/fields ////////$fd['hidden_fields']/=/array(

    ////////////'ACT'/=>/$thisE>EEE>functionsE>fetch_action_id( /////////////////////'Low_likes',/'toggle_like'), ////////////'EID'/=>/$entry_id ////////); ///////////Wrap/form/around/tagdata ////////$tagdata/=/$thisE>EEE>functionsE>form_declaration($fd) /////////////////./$tagdata /////////////////./'</form>'; ////}
  18. ///Wrap/tagdata/in/a/form/if/needed if/($form) { ///////Initiate/data/array/for/form/creation ////$fd/=/array( ////////'id'////=>/$thisE>EEE>TMPLE>fetch_param('form_id'), ////////'class'/=>/$thisE>EEE>TMPLE>fetch_param('form_class') ////); ///////Define/default/hidden/fields ////$fd['hidden_fields']/=/array(

    ////////'ACT'/=>/$thisE>EEE>functionsE>fetch_action_id( /////////////////'Low_likes',/'toggle_like'), ////////'EID'/=>/$entry_id ////); ///////Wrap/form/around/tagdata ////$tagdata/=/$thisE>EEE>functionsE>form_declaration($fd) /////////////./$tagdata /////////////./'</form>'; }
  19. ///////Wrap/tagdata/in/a/form/if/needed ////if/($form) ////{ ///////////Initiate/data/array/for/form/creation ////////$fd/=/array( ////////////'id'////=>/$thisE>EEE>TMPLE>fetch_param('form_id'), ////////////'class'/=>/$thisE>EEE>TMPLE>fetch_param('form_class') ////////); ///////////Define/default/hidden/fields ////////$fd['hidden_fields']/=/array(

    ////////////'ACT'/=>/$thisE>EEE>functionsE>fetch_action_id( /////////////////////'Low_likes',/'toggle_like'), ////////////'EID'/=>/$entry_id ////////); ///////////Wrap/form/around/tagdata ////////$tagdata/=/$thisE>EEE>functionsE>form_declaration($fd) /////////////////./$tagdata /////////////////./'</form>'; ////}
  20. /** /*/ACT:/(un)like/posted/entry /*/ public/function/toggle_like() { ///////Get/entry_id/from/POST,/member_id/from/session ////$entry_id/=/$thisE>EEE>inputE>post('EID'); ////$member_id/=/$thisE>EEE>sessionE>userdata('member_id'); ///////Only/continue/if/both/are/valid ////if/($entry_id/&&/$member_id)

    ////{ ///////////Create/data/array/for/DB/queries ////////$data/=/array( ////////////'entry_id'//=>/$entry_id, ////////////'member_id'/=>/$member_id ////////); ///////////Is/entry/liked/by/member? ////////$thisE>EEE>dbE>where($data); ////////$liked/=/$thisE>EEE>dbE>count_all_results('low_likes');
  21. ///////////If/liked,/delete/entry;/else,/insert/entry ////////if/($liked) ////////{ ////////////$thisE>EEE>dbE>delete('low_likes',/$data); ////////} ////////else ////////{ ////////////$data['like_date']/=/$thisE>EEE>localizeE>now; ////////////$thisE>EEE>dbE>insert('low_likes',/$data); ////////}

    ///////////Cater/for/Ajax/requests ////////if/(AJAX_REQUEST) ////////{ ////////////die($liked/?/'E1'/:/'1'); ////////} ////} //////// ///////Return/to/previous/page ////$thisE>EEE>functionsE>redirect( ////////$thisE>EEE>sessionE>tracker[1] ////);
  22. {exp:channel:entries/channel="products"/limit="25"} / <h2>{title}</h2> / {exp:low_likes:show/entry_id="{entry_id}"/form="yes"} / / <p> / /

    / {total_likes}/Like{if/total_likes/!=/1}s{/if} / / / {if/has_form} / / / / <button/type="submit"> / / / / / {if/is_liked}Unlike{if:else}Like{/if} / / / / </button> / / / {/if} / / </p> / {/exp:low_likes:show} {/exp:channel:entries}
  23. 0.0005 SELECT `member_id` FROM (`exp_low_likes`) WHERE `entry_id` = '29570' 0.0004

    SELECT `member_id` FROM (`exp_low_likes`) WHERE `entry_id` = '29533' 0.0003 SELECT `member_id` FROM (`exp_low_likes`) WHERE `entry_id` = '29532' 0.0003 SELECT `member_id` FROM (`exp_low_likes`) WHERE `entry_id` = '29530' 0.0003 SELECT `member_id` FROM (`exp_low_likes`) WHERE `entry_id` = '29531' 0.0004 SELECT `member_id` FROM (`exp_low_likes`) WHERE `entry_id` = '29529' 0.0004 SELECT `member_id`
  24. /** /*/Add/likeEvars/to/channel:entries/data /*/ public/function/channel_entries_query_result($obj,/$entries) { ///////Get/the/latest/version/of/$entries ////if/($thisE>EEE>extensionsE>last_call/!==/FALSE) ////{ ////////$entries/=/$thisE>EEE>extensionsE>last_call; ////}

    ///////Is/there/a/low_likes/tag/here? ////if/(/!/strpos($thisE>EEE>TMPLE>tagdata,/'exp:low_likes:')) ////{ ////////return/$entries; ////} ///////Initiate/likes/for/all/given/entry_ids ////$likes/=/array(); ////foreach/($entries/AS/$row) ////{
  25. 0.0003 SELECT `entry_id`, `member_id` FROM (`exp_low_likes`) WHERE `entry_id` IN (29570,

    29533, 29532, 29530, 29531, 29529, 29528, 29527, 29526, 29525, 29524, 29523, 29522, 29521, 29520, 29519, 29518, 29517, 29516, 29515, 29514, 29513, 29512, 29511, 29510)
  26. /** /*/Show/entries/liked/by/logged/in/member /*/ public/function/entries() { ///////Get/entry/ids/for/logged/in/member/from/DB ///////Set/fixed_order/parameter/based/on/ids ///////Load/&/call/the/Channel/module } /**

    /*/Show/entries/with/most/likes /*/ public/function/popular() { ///////Get/entry/ids/and/member/count/from/DB ///////Set/fixed_order/parameter/based/on/ids ///////Load/&/call/the/Channel/module }
  27. /** /*/Show/entries/liked/by/logged/in/member /*/ public/function/entries() { ///////Get/entry/ids/for/logged/in/member/from/DB ///////Set/fixed_order/parameter/based/on/ids ///////Load/&/call/the/Channel/module } /**

    /*/Show/entries/with/most/likes /*/ public/function/popular() { ///////Get/entry/ids/and/member/count/from/DB ///////Set/fixed_order/parameter/based/on/ids ///////Load/&/call/the/Channel/module }
  28. /** /*/Show/entries/liked/by/logged/in/member /*/ public/function/entries() { ///////Get/entry/ids/for/logged/in/member/from/DB } /** /*/Show/entries/with/most/likes /*/

    public/function/popular() { ///////Get/entry/ids/and/member/count/from/DB } /** /*/Load/and/call/Channel::entries() /*/ private/function/_get_entries($entry_ids) { ///////Set/fixed_order/parameter/based/on/ids ///////Load/&/call/the/Channel/module
  29. /*/ public/function/entries() { ///////Get/entry/ids/for/logged/in/member/from/DB } /** /*/Show/entries/with/most/likes /*/ public/function/popular() {

    ///////Get/entry/ids/and/member/count/from/DB } /** /*/Load/and/call/Channel::entries() /*/ private/function/_get_entries($entry_ids) { ///////Set/fixed_order/parameter/based/on/ids ///////Load/&/call/the/Channel/module }
  30. Other ways to not repeat yourself » Use a “base”

    class and inheritance » Create your own libraries, models, and helpers » Use “shortcuts” for frequently requested config items Simplify, but also keep it simple » Choose readability over elegance » Separate add-on data from the core