Jump to content

Esther E.

Invision Community Team
  • Posts

    88
  • Joined

  • Last visited

  • Days Won

    2

Reputation Activity

  1. Like
    Esther E. got a reaction from ReyDev for an entry, IC5: Menus   
    Action and Moderation Menus can be one of the most tedious development tasks, while also being critical to user experience.
    For example, we may add support for pinning/unpinning content, then we need to remember to include the ability to pin/unpin that content in all the HTML templates. 3rd party developers add screens inside their applications, and then they need to add a link to the User Menu to make that accessible.
    With Invision Community 4, this would require a template hook targeting fairly complex classes and children that was susceptible to structural changes to templates between versions.
    In Invision Community 5, we have made significant changes to menu creation in order to streamline the process, and also to allow 3rd party developers better accessibility.
     
    New Helper Classes
    Menus are now built using helper classes, rather than relying on lengthy HTML templates. A menu is created by initializing a new Menu object.
    $menu = new Menu( 'AccountMenu' ); We can then add elements to the menu.
    $menu->addTitleField( 'menu_content' ); $menu->add( new Link( $member->url(), 'menu_profile', icon:'fa-solid fa-user' ) ); $menu->addSeparator(); $menu->add( new Link( Url::internal( "app=core&module=system&controller=markread", "front", "mark_site_as_read" )->csrf()->addRef( Request::i()->url() ), 'mark_site_read', dataAttributes: [ 'data-action' => 'markSiteRead', 'data-controller' => 'core.front.core.markRead' ], icon: 'fa-solid fa-eye' ) ); The most common menu element is the Link. This will generate the appropriate <li> element within the menu. You can define the URL, CSS class (default is ipsMenu_item), data attributes, icon, title, among other properties.
    Titles and Separators are added using the Menu::addTitleField() and Menu::addSeparator() methods respectively.
    You can also insert custom HTML into the menu using the Menu::addHtml() method.
    In your HTML template, you would then simply display the menu object as a string.
    {{if $menu->hasContent()}} {$menu|raw} {{endif}} The menu templates will include the link that opens the menu, as well as the menu itself.
    You'll notice that the above contains a call to the hasContent() method. This method will check if the menu object has any elements inside; if a user does not have permission to any of the elements, nothing will be displayed.
     
    Content Menus
    Almost all content items and comments (and reviews) require some kind of menu for moderation. Previously, this meant creating large chunks of redundant HTML code throughout the codebase. We've implemented \IPS\Content\Item::menu() and \IPS\Content\Comment::menu() to build these menus in a central location. The new methods include checks for whether a feature is in use and whether the user has permission to perform this action.
    Example:
    if( IPS::classUsesTrait( $this, 'IPS\Content\Pinnable' ) AND $this->canPin( $member ) ) { $links['pin'] = new ContentMenuLink( $this->url()->csrf()->setQueryString( array( 'do' => 'moderate', 'action' => 'pin' ) ), 'pin' ); } Now, the HTML template simply contains:
    {$entry->menu()|raw} (Note: Yes, these content menus can be extended using... you guessed it. UI Extensions.)
     
    Other Menus
    We have also migrated the following menus to the new structure:
    User Menu Mobile Menu Create Menu  
    Badges
    Another area with redundant HTML was our content badges. For example, pinned topics will display an icon on both the topic list and the topic header. We have created helper classes for badges and badge icons to centralize this logic. Within the new Item::badges() method, we build an array of icons that will be shown.
    if( IPS::classUsesTrait( $this, Featurable::class ) AND $this->isFeatured() ) { $return['featured'] = new Icon( Badge::BADGE_POSITIVE, 'fa-solid fa-star', Member::loggedIn()->language()->addToStack( 'featured' ) ); } The above generates a badge icon with the badge type ipsBadge--positive (constants have been declared for all common types, but any class can be used), the star icon, and the "featured" language string as the tooltip.
    In our template HTML, we now have
    <div class='ipsBadges'> {{foreach $entry->badges() as $badge}}{$badge|raw}{{endforeach}} </div>  
    UserMenu Extension
    Now that we've moved the menus into the codebase and out of the templates, we needed a way to allow 3rd party developers to insert their own menu items. We've introduced a new extension called UserMenu.
    The UserMenu extension contains the following methods:
    accountMenu
    This method allows you to add menu elements to the user menu. The method includes a parameter to allow you to add elements in one of 3 places within the menu: content, settings, or logout. mobileMenu
    Allows you to add elements to the mobile navigation menu. All elements are inserted before the "sign out" option. createMenu
    Replaces the CreateMenu extension, which has been deprecated. accountSettingsLinks
    Allows you to add links to the inner sidebar in the Account Settings overview tab (under the link for Notification Settings). Note: No, this does not add tabs to the Account Settings page, but fear not, that's coming. (Not in the UI Extension.)
    userNav
    This method will insert HTML in the user bar, to the left of the Create Menu. mobileNav
    Similar to the userNav method, but inserts HTML into the mobile navigation header.  
    What do you think of our changes to the Menus? Let us know in the comments below.
  2. Like
    Esther E. got a reaction from ReyDev for an entry, IC5: Introduction to Listeners   
    I'm very excited to be posting my first blog entry for IPS, and thrilled that this is what I get to post.
    When we started planning the new development tools for v5, we looked at existing resources - from the marketplace, from some contributors, and from our own managed clients - and asked, what is it that developers find themselves doing the most often? Matt’s previous blog entry gave a brief summary of some of those functions. Today’s blog entry is going to focus on one of our powerful new tools, Listeners.
    One common reason for a code hook was to execute custom code when a specific action occurs. For example, when a topic is created, or when a new reply is posted. Listeners allow you to execute your code at key points in the workflow, without the worry of finding exactly the right hook location.
    Here is an excerpt from the BlogComment listener in our Cloud application:

     
    Creating Listeners
    Let’s start with the application’s Developer Center.

    We’ve added a new tab for Listeners. This tab lists all your existing listeners, as well as the class on which it is listening.
    You’ll notice that each listener can only observe a single class. While this may seem slightly tedious, the idea here was that as a developer, you should be conscious of what class you are working with. It also eliminates the need to check what type of object is being passed preventing accidents and unintended consequences when missing class checks.
    When you add a listener, there are several listener types available. We will discuss each one in detail.

     
    Content Listener
    This listener is fired on an Item or Comment (or Review) class. When adding a Content Listener, you must specify the class you are observing. When you hit Save, there is a validation check in place to ensure that the class exists, and that it is a valid class for the selected listener type.
    Content Listeners have the following methods available. All methods are included in the default listener file that is generated.
    onLoad
    Triggered when an object is loaded (in Content::constructFromData) NOTE: Be careful! This event could be fired at unexpected times. If you’re executing code here, you may want to check the dispatcher instance to verify that your code is running where you expect.
    onBeforeCreateOrEdit
    Fired when a form is submitted, before it is saved. This is the equivalent of Item::processBeforeCreate and Item::processBeforeEdit. This method includes a parameter to indicate whether this is a create or edit. onCreateOrEdit
    Fired after a form is saved. This is the equivalent of Item::processAfterCreate and Item::processAfterEdit. This method includes a parameter to indicate whether this is a create or edit. onDelete
    Fired after an object is deleted. onStatusChange
    Fired when any moderation action occurs. For example, when an item is pinned/unpinned, locked/unlocked. The moderation action is passed as a parameter so that your code can take that into account. onMerge
    Triggered AFTER an item is merged. onItemView
    Triggered when an item is viewed and the view count is incremented.  
    Invoice Listener
    An Invoice listener is fired only on the \IPS\nexus\Invoice class. This listener does not require you to specify a class to extend.
    The Invoice listener includes a single method, onStatusChange. This is fired when the invoice is saved and the status changes (pending/paid/expired/canceled).
     
    Member Listener
    The Member listener will be familiar to many of you. We have taken the MemberSync extension and moved it in its entirety to a listener, as it was a better fit for this structure. All the previous methods that were available are still available here. We have also added the following new methods:
    onJoinClub
    Fired when a member joins a club. If approval is required, this is fired after they are approved. onLeaveClub
    Fired when a member leaves or is removed from a club. onEventRsvp
    Fired when a member responds to an event. The response is included as a parameter to this method.  
    Commerce Package Listener
    The Commerce Package listener is fired on any implementation of \IPS\nexus\Invoice\Item. You must specify the class that you are observing.
    The methods are the same as those that are available in an item extension.
    onCancel onChange onDelete onExpireWarning onExpire onInvoiceCancel onPaid onPurchaseGenerated onReactivate onRenew onTransfer onUnpaid  
    Some Technical Notes
    All listener files will be generated in a “listeners” directory within your application. Your application’s data directory will include a listeners.json file that defines all your listeners. The json file is automatically generated when you add/remove a listener. All listener methods are optional. The default class that is generated will include all available methods, but they do not have to be present in your file. All listener methods are return type void. If a listener method fails, an exception will be thrown when IN_DEV is enabled. In production, the exception will be logged with a reference to the listener class and the event on which the exception occurred.  
    Firing Events
    Your code can fire any existing event using the Event::fire method.
    Example:
    Event::fire( 'onBeforeCreateOrEdit', $comment, array( $values ) ); The Event::fire method is called statically and accepts an event name, the object on which the event will be fired, and an array of additional parameters (varies according to the event that you are triggering).
    This concludes our introduction to Listeners. We do intend to implement additional listener types and events based on your feedback and as the need arises.
  3. Like
    Esther E. got a reaction from Marc Stridgen for an entry, IC5: Menus   
    Action and Moderation Menus can be one of the most tedious development tasks, while also being critical to user experience.
    For example, we may add support for pinning/unpinning content, then we need to remember to include the ability to pin/unpin that content in all the HTML templates. 3rd party developers add screens inside their applications, and then they need to add a link to the User Menu to make that accessible.
    With Invision Community 4, this would require a template hook targeting fairly complex classes and children that was susceptible to structural changes to templates between versions.
    In Invision Community 5, we have made significant changes to menu creation in order to streamline the process, and also to allow 3rd party developers better accessibility.
     
    New Helper Classes
    Menus are now built using helper classes, rather than relying on lengthy HTML templates. A menu is created by initializing a new Menu object.
    $menu = new Menu( 'AccountMenu' ); We can then add elements to the menu.
    $menu->addTitleField( 'menu_content' ); $menu->add( new Link( $member->url(), 'menu_profile', icon:'fa-solid fa-user' ) ); $menu->addSeparator(); $menu->add( new Link( Url::internal( "app=core&module=system&controller=markread", "front", "mark_site_as_read" )->csrf()->addRef( Request::i()->url() ), 'mark_site_read', dataAttributes: [ 'data-action' => 'markSiteRead', 'data-controller' => 'core.front.core.markRead' ], icon: 'fa-solid fa-eye' ) ); The most common menu element is the Link. This will generate the appropriate <li> element within the menu. You can define the URL, CSS class (default is ipsMenu_item), data attributes, icon, title, among other properties.
    Titles and Separators are added using the Menu::addTitleField() and Menu::addSeparator() methods respectively.
    You can also insert custom HTML into the menu using the Menu::addHtml() method.
    In your HTML template, you would then simply display the menu object as a string.
    {{if $menu->hasContent()}} {$menu|raw} {{endif}} The menu templates will include the link that opens the menu, as well as the menu itself.
    You'll notice that the above contains a call to the hasContent() method. This method will check if the menu object has any elements inside; if a user does not have permission to any of the elements, nothing will be displayed.
     
    Content Menus
    Almost all content items and comments (and reviews) require some kind of menu for moderation. Previously, this meant creating large chunks of redundant HTML code throughout the codebase. We've implemented \IPS\Content\Item::menu() and \IPS\Content\Comment::menu() to build these menus in a central location. The new methods include checks for whether a feature is in use and whether the user has permission to perform this action.
    Example:
    if( IPS::classUsesTrait( $this, 'IPS\Content\Pinnable' ) AND $this->canPin( $member ) ) { $links['pin'] = new ContentMenuLink( $this->url()->csrf()->setQueryString( array( 'do' => 'moderate', 'action' => 'pin' ) ), 'pin' ); } Now, the HTML template simply contains:
    {$entry->menu()|raw} (Note: Yes, these content menus can be extended using... you guessed it. UI Extensions.)
     
    Other Menus
    We have also migrated the following menus to the new structure:
    User Menu Mobile Menu Create Menu  
    Badges
    Another area with redundant HTML was our content badges. For example, pinned topics will display an icon on both the topic list and the topic header. We have created helper classes for badges and badge icons to centralize this logic. Within the new Item::badges() method, we build an array of icons that will be shown.
    if( IPS::classUsesTrait( $this, Featurable::class ) AND $this->isFeatured() ) { $return['featured'] = new Icon( Badge::BADGE_POSITIVE, 'fa-solid fa-star', Member::loggedIn()->language()->addToStack( 'featured' ) ); } The above generates a badge icon with the badge type ipsBadge--positive (constants have been declared for all common types, but any class can be used), the star icon, and the "featured" language string as the tooltip.
    In our template HTML, we now have
    <div class='ipsBadges'> {{foreach $entry->badges() as $badge}}{$badge|raw}{{endforeach}} </div>  
    UserMenu Extension
    Now that we've moved the menus into the codebase and out of the templates, we needed a way to allow 3rd party developers to insert their own menu items. We've introduced a new extension called UserMenu.
    The UserMenu extension contains the following methods:
    accountMenu
    This method allows you to add menu elements to the user menu. The method includes a parameter to allow you to add elements in one of 3 places within the menu: content, settings, or logout. mobileMenu
    Allows you to add elements to the mobile navigation menu. All elements are inserted before the "sign out" option. createMenu
    Replaces the CreateMenu extension, which has been deprecated. accountSettingsLinks
    Allows you to add links to the inner sidebar in the Account Settings overview tab (under the link for Notification Settings). Note: No, this does not add tabs to the Account Settings page, but fear not, that's coming. (Not in the UI Extension.)
    userNav
    This method will insert HTML in the user bar, to the left of the Create Menu. mobileNav
    Similar to the userNav method, but inserts HTML into the mobile navigation header.  
    What do you think of our changes to the Menus? Let us know in the comments below.
  4. Like
    Esther E. got a reaction from DawPi for an entry, IC5: Menus   
    Action and Moderation Menus can be one of the most tedious development tasks, while also being critical to user experience.
    For example, we may add support for pinning/unpinning content, then we need to remember to include the ability to pin/unpin that content in all the HTML templates. 3rd party developers add screens inside their applications, and then they need to add a link to the User Menu to make that accessible.
    With Invision Community 4, this would require a template hook targeting fairly complex classes and children that was susceptible to structural changes to templates between versions.
    In Invision Community 5, we have made significant changes to menu creation in order to streamline the process, and also to allow 3rd party developers better accessibility.
     
    New Helper Classes
    Menus are now built using helper classes, rather than relying on lengthy HTML templates. A menu is created by initializing a new Menu object.
    $menu = new Menu( 'AccountMenu' ); We can then add elements to the menu.
    $menu->addTitleField( 'menu_content' ); $menu->add( new Link( $member->url(), 'menu_profile', icon:'fa-solid fa-user' ) ); $menu->addSeparator(); $menu->add( new Link( Url::internal( "app=core&module=system&controller=markread", "front", "mark_site_as_read" )->csrf()->addRef( Request::i()->url() ), 'mark_site_read', dataAttributes: [ 'data-action' => 'markSiteRead', 'data-controller' => 'core.front.core.markRead' ], icon: 'fa-solid fa-eye' ) ); The most common menu element is the Link. This will generate the appropriate <li> element within the menu. You can define the URL, CSS class (default is ipsMenu_item), data attributes, icon, title, among other properties.
    Titles and Separators are added using the Menu::addTitleField() and Menu::addSeparator() methods respectively.
    You can also insert custom HTML into the menu using the Menu::addHtml() method.
    In your HTML template, you would then simply display the menu object as a string.
    {{if $menu->hasContent()}} {$menu|raw} {{endif}} The menu templates will include the link that opens the menu, as well as the menu itself.
    You'll notice that the above contains a call to the hasContent() method. This method will check if the menu object has any elements inside; if a user does not have permission to any of the elements, nothing will be displayed.
     
    Content Menus
    Almost all content items and comments (and reviews) require some kind of menu for moderation. Previously, this meant creating large chunks of redundant HTML code throughout the codebase. We've implemented \IPS\Content\Item::menu() and \IPS\Content\Comment::menu() to build these menus in a central location. The new methods include checks for whether a feature is in use and whether the user has permission to perform this action.
    Example:
    if( IPS::classUsesTrait( $this, 'IPS\Content\Pinnable' ) AND $this->canPin( $member ) ) { $links['pin'] = new ContentMenuLink( $this->url()->csrf()->setQueryString( array( 'do' => 'moderate', 'action' => 'pin' ) ), 'pin' ); } Now, the HTML template simply contains:
    {$entry->menu()|raw} (Note: Yes, these content menus can be extended using... you guessed it. UI Extensions.)
     
    Other Menus
    We have also migrated the following menus to the new structure:
    User Menu Mobile Menu Create Menu  
    Badges
    Another area with redundant HTML was our content badges. For example, pinned topics will display an icon on both the topic list and the topic header. We have created helper classes for badges and badge icons to centralize this logic. Within the new Item::badges() method, we build an array of icons that will be shown.
    if( IPS::classUsesTrait( $this, Featurable::class ) AND $this->isFeatured() ) { $return['featured'] = new Icon( Badge::BADGE_POSITIVE, 'fa-solid fa-star', Member::loggedIn()->language()->addToStack( 'featured' ) ); } The above generates a badge icon with the badge type ipsBadge--positive (constants have been declared for all common types, but any class can be used), the star icon, and the "featured" language string as the tooltip.
    In our template HTML, we now have
    <div class='ipsBadges'> {{foreach $entry->badges() as $badge}}{$badge|raw}{{endforeach}} </div>  
    UserMenu Extension
    Now that we've moved the menus into the codebase and out of the templates, we needed a way to allow 3rd party developers to insert their own menu items. We've introduced a new extension called UserMenu.
    The UserMenu extension contains the following methods:
    accountMenu
    This method allows you to add menu elements to the user menu. The method includes a parameter to allow you to add elements in one of 3 places within the menu: content, settings, or logout. mobileMenu
    Allows you to add elements to the mobile navigation menu. All elements are inserted before the "sign out" option. createMenu
    Replaces the CreateMenu extension, which has been deprecated. accountSettingsLinks
    Allows you to add links to the inner sidebar in the Account Settings overview tab (under the link for Notification Settings). Note: No, this does not add tabs to the Account Settings page, but fear not, that's coming. (Not in the UI Extension.)
    userNav
    This method will insert HTML in the user bar, to the left of the Create Menu. mobileNav
    Similar to the userNav method, but inserts HTML into the mobile navigation header.  
    What do you think of our changes to the Menus? Let us know in the comments below.
  5. Like
    Esther E. got a reaction from Miss_B for an entry, IC5: Menus   
    Action and Moderation Menus can be one of the most tedious development tasks, while also being critical to user experience.
    For example, we may add support for pinning/unpinning content, then we need to remember to include the ability to pin/unpin that content in all the HTML templates. 3rd party developers add screens inside their applications, and then they need to add a link to the User Menu to make that accessible.
    With Invision Community 4, this would require a template hook targeting fairly complex classes and children that was susceptible to structural changes to templates between versions.
    In Invision Community 5, we have made significant changes to menu creation in order to streamline the process, and also to allow 3rd party developers better accessibility.
     
    New Helper Classes
    Menus are now built using helper classes, rather than relying on lengthy HTML templates. A menu is created by initializing a new Menu object.
    $menu = new Menu( 'AccountMenu' ); We can then add elements to the menu.
    $menu->addTitleField( 'menu_content' ); $menu->add( new Link( $member->url(), 'menu_profile', icon:'fa-solid fa-user' ) ); $menu->addSeparator(); $menu->add( new Link( Url::internal( "app=core&module=system&controller=markread", "front", "mark_site_as_read" )->csrf()->addRef( Request::i()->url() ), 'mark_site_read', dataAttributes: [ 'data-action' => 'markSiteRead', 'data-controller' => 'core.front.core.markRead' ], icon: 'fa-solid fa-eye' ) ); The most common menu element is the Link. This will generate the appropriate <li> element within the menu. You can define the URL, CSS class (default is ipsMenu_item), data attributes, icon, title, among other properties.
    Titles and Separators are added using the Menu::addTitleField() and Menu::addSeparator() methods respectively.
    You can also insert custom HTML into the menu using the Menu::addHtml() method.
    In your HTML template, you would then simply display the menu object as a string.
    {{if $menu->hasContent()}} {$menu|raw} {{endif}} The menu templates will include the link that opens the menu, as well as the menu itself.
    You'll notice that the above contains a call to the hasContent() method. This method will check if the menu object has any elements inside; if a user does not have permission to any of the elements, nothing will be displayed.
     
    Content Menus
    Almost all content items and comments (and reviews) require some kind of menu for moderation. Previously, this meant creating large chunks of redundant HTML code throughout the codebase. We've implemented \IPS\Content\Item::menu() and \IPS\Content\Comment::menu() to build these menus in a central location. The new methods include checks for whether a feature is in use and whether the user has permission to perform this action.
    Example:
    if( IPS::classUsesTrait( $this, 'IPS\Content\Pinnable' ) AND $this->canPin( $member ) ) { $links['pin'] = new ContentMenuLink( $this->url()->csrf()->setQueryString( array( 'do' => 'moderate', 'action' => 'pin' ) ), 'pin' ); } Now, the HTML template simply contains:
    {$entry->menu()|raw} (Note: Yes, these content menus can be extended using... you guessed it. UI Extensions.)
     
    Other Menus
    We have also migrated the following menus to the new structure:
    User Menu Mobile Menu Create Menu  
    Badges
    Another area with redundant HTML was our content badges. For example, pinned topics will display an icon on both the topic list and the topic header. We have created helper classes for badges and badge icons to centralize this logic. Within the new Item::badges() method, we build an array of icons that will be shown.
    if( IPS::classUsesTrait( $this, Featurable::class ) AND $this->isFeatured() ) { $return['featured'] = new Icon( Badge::BADGE_POSITIVE, 'fa-solid fa-star', Member::loggedIn()->language()->addToStack( 'featured' ) ); } The above generates a badge icon with the badge type ipsBadge--positive (constants have been declared for all common types, but any class can be used), the star icon, and the "featured" language string as the tooltip.
    In our template HTML, we now have
    <div class='ipsBadges'> {{foreach $entry->badges() as $badge}}{$badge|raw}{{endforeach}} </div>  
    UserMenu Extension
    Now that we've moved the menus into the codebase and out of the templates, we needed a way to allow 3rd party developers to insert their own menu items. We've introduced a new extension called UserMenu.
    The UserMenu extension contains the following methods:
    accountMenu
    This method allows you to add menu elements to the user menu. The method includes a parameter to allow you to add elements in one of 3 places within the menu: content, settings, or logout. mobileMenu
    Allows you to add elements to the mobile navigation menu. All elements are inserted before the "sign out" option. createMenu
    Replaces the CreateMenu extension, which has been deprecated. accountSettingsLinks
    Allows you to add links to the inner sidebar in the Account Settings overview tab (under the link for Notification Settings). Note: No, this does not add tabs to the Account Settings page, but fear not, that's coming. (Not in the UI Extension.)
    userNav
    This method will insert HTML in the user bar, to the left of the Create Menu. mobileNav
    Similar to the userNav method, but inserts HTML into the mobile navigation header.  
    What do you think of our changes to the Menus? Let us know in the comments below.
  6. Like
    Esther E. got a reaction from SeNioR- for an entry, IC5: Menus   
    Action and Moderation Menus can be one of the most tedious development tasks, while also being critical to user experience.
    For example, we may add support for pinning/unpinning content, then we need to remember to include the ability to pin/unpin that content in all the HTML templates. 3rd party developers add screens inside their applications, and then they need to add a link to the User Menu to make that accessible.
    With Invision Community 4, this would require a template hook targeting fairly complex classes and children that was susceptible to structural changes to templates between versions.
    In Invision Community 5, we have made significant changes to menu creation in order to streamline the process, and also to allow 3rd party developers better accessibility.
     
    New Helper Classes
    Menus are now built using helper classes, rather than relying on lengthy HTML templates. A menu is created by initializing a new Menu object.
    $menu = new Menu( 'AccountMenu' ); We can then add elements to the menu.
    $menu->addTitleField( 'menu_content' ); $menu->add( new Link( $member->url(), 'menu_profile', icon:'fa-solid fa-user' ) ); $menu->addSeparator(); $menu->add( new Link( Url::internal( "app=core&module=system&controller=markread", "front", "mark_site_as_read" )->csrf()->addRef( Request::i()->url() ), 'mark_site_read', dataAttributes: [ 'data-action' => 'markSiteRead', 'data-controller' => 'core.front.core.markRead' ], icon: 'fa-solid fa-eye' ) ); The most common menu element is the Link. This will generate the appropriate <li> element within the menu. You can define the URL, CSS class (default is ipsMenu_item), data attributes, icon, title, among other properties.
    Titles and Separators are added using the Menu::addTitleField() and Menu::addSeparator() methods respectively.
    You can also insert custom HTML into the menu using the Menu::addHtml() method.
    In your HTML template, you would then simply display the menu object as a string.
    {{if $menu->hasContent()}} {$menu|raw} {{endif}} The menu templates will include the link that opens the menu, as well as the menu itself.
    You'll notice that the above contains a call to the hasContent() method. This method will check if the menu object has any elements inside; if a user does not have permission to any of the elements, nothing will be displayed.
     
    Content Menus
    Almost all content items and comments (and reviews) require some kind of menu for moderation. Previously, this meant creating large chunks of redundant HTML code throughout the codebase. We've implemented \IPS\Content\Item::menu() and \IPS\Content\Comment::menu() to build these menus in a central location. The new methods include checks for whether a feature is in use and whether the user has permission to perform this action.
    Example:
    if( IPS::classUsesTrait( $this, 'IPS\Content\Pinnable' ) AND $this->canPin( $member ) ) { $links['pin'] = new ContentMenuLink( $this->url()->csrf()->setQueryString( array( 'do' => 'moderate', 'action' => 'pin' ) ), 'pin' ); } Now, the HTML template simply contains:
    {$entry->menu()|raw} (Note: Yes, these content menus can be extended using... you guessed it. UI Extensions.)
     
    Other Menus
    We have also migrated the following menus to the new structure:
    User Menu Mobile Menu Create Menu  
    Badges
    Another area with redundant HTML was our content badges. For example, pinned topics will display an icon on both the topic list and the topic header. We have created helper classes for badges and badge icons to centralize this logic. Within the new Item::badges() method, we build an array of icons that will be shown.
    if( IPS::classUsesTrait( $this, Featurable::class ) AND $this->isFeatured() ) { $return['featured'] = new Icon( Badge::BADGE_POSITIVE, 'fa-solid fa-star', Member::loggedIn()->language()->addToStack( 'featured' ) ); } The above generates a badge icon with the badge type ipsBadge--positive (constants have been declared for all common types, but any class can be used), the star icon, and the "featured" language string as the tooltip.
    In our template HTML, we now have
    <div class='ipsBadges'> {{foreach $entry->badges() as $badge}}{$badge|raw}{{endforeach}} </div>  
    UserMenu Extension
    Now that we've moved the menus into the codebase and out of the templates, we needed a way to allow 3rd party developers to insert their own menu items. We've introduced a new extension called UserMenu.
    The UserMenu extension contains the following methods:
    accountMenu
    This method allows you to add menu elements to the user menu. The method includes a parameter to allow you to add elements in one of 3 places within the menu: content, settings, or logout. mobileMenu
    Allows you to add elements to the mobile navigation menu. All elements are inserted before the "sign out" option. createMenu
    Replaces the CreateMenu extension, which has been deprecated. accountSettingsLinks
    Allows you to add links to the inner sidebar in the Account Settings overview tab (under the link for Notification Settings). Note: No, this does not add tabs to the Account Settings page, but fear not, that's coming. (Not in the UI Extension.)
    userNav
    This method will insert HTML in the user bar, to the left of the Create Menu. mobileNav
    Similar to the userNav method, but inserts HTML into the mobile navigation header.  
    What do you think of our changes to the Menus? Let us know in the comments below.
  7. Like
    Esther E. got a reaction from Adriano Faria for an entry, IC5: Menus   
    Action and Moderation Menus can be one of the most tedious development tasks, while also being critical to user experience.
    For example, we may add support for pinning/unpinning content, then we need to remember to include the ability to pin/unpin that content in all the HTML templates. 3rd party developers add screens inside their applications, and then they need to add a link to the User Menu to make that accessible.
    With Invision Community 4, this would require a template hook targeting fairly complex classes and children that was susceptible to structural changes to templates between versions.
    In Invision Community 5, we have made significant changes to menu creation in order to streamline the process, and also to allow 3rd party developers better accessibility.
     
    New Helper Classes
    Menus are now built using helper classes, rather than relying on lengthy HTML templates. A menu is created by initializing a new Menu object.
    $menu = new Menu( 'AccountMenu' ); We can then add elements to the menu.
    $menu->addTitleField( 'menu_content' ); $menu->add( new Link( $member->url(), 'menu_profile', icon:'fa-solid fa-user' ) ); $menu->addSeparator(); $menu->add( new Link( Url::internal( "app=core&module=system&controller=markread", "front", "mark_site_as_read" )->csrf()->addRef( Request::i()->url() ), 'mark_site_read', dataAttributes: [ 'data-action' => 'markSiteRead', 'data-controller' => 'core.front.core.markRead' ], icon: 'fa-solid fa-eye' ) ); The most common menu element is the Link. This will generate the appropriate <li> element within the menu. You can define the URL, CSS class (default is ipsMenu_item), data attributes, icon, title, among other properties.
    Titles and Separators are added using the Menu::addTitleField() and Menu::addSeparator() methods respectively.
    You can also insert custom HTML into the menu using the Menu::addHtml() method.
    In your HTML template, you would then simply display the menu object as a string.
    {{if $menu->hasContent()}} {$menu|raw} {{endif}} The menu templates will include the link that opens the menu, as well as the menu itself.
    You'll notice that the above contains a call to the hasContent() method. This method will check if the menu object has any elements inside; if a user does not have permission to any of the elements, nothing will be displayed.
     
    Content Menus
    Almost all content items and comments (and reviews) require some kind of menu for moderation. Previously, this meant creating large chunks of redundant HTML code throughout the codebase. We've implemented \IPS\Content\Item::menu() and \IPS\Content\Comment::menu() to build these menus in a central location. The new methods include checks for whether a feature is in use and whether the user has permission to perform this action.
    Example:
    if( IPS::classUsesTrait( $this, 'IPS\Content\Pinnable' ) AND $this->canPin( $member ) ) { $links['pin'] = new ContentMenuLink( $this->url()->csrf()->setQueryString( array( 'do' => 'moderate', 'action' => 'pin' ) ), 'pin' ); } Now, the HTML template simply contains:
    {$entry->menu()|raw} (Note: Yes, these content menus can be extended using... you guessed it. UI Extensions.)
     
    Other Menus
    We have also migrated the following menus to the new structure:
    User Menu Mobile Menu Create Menu  
    Badges
    Another area with redundant HTML was our content badges. For example, pinned topics will display an icon on both the topic list and the topic header. We have created helper classes for badges and badge icons to centralize this logic. Within the new Item::badges() method, we build an array of icons that will be shown.
    if( IPS::classUsesTrait( $this, Featurable::class ) AND $this->isFeatured() ) { $return['featured'] = new Icon( Badge::BADGE_POSITIVE, 'fa-solid fa-star', Member::loggedIn()->language()->addToStack( 'featured' ) ); } The above generates a badge icon with the badge type ipsBadge--positive (constants have been declared for all common types, but any class can be used), the star icon, and the "featured" language string as the tooltip.
    In our template HTML, we now have
    <div class='ipsBadges'> {{foreach $entry->badges() as $badge}}{$badge|raw}{{endforeach}} </div>  
    UserMenu Extension
    Now that we've moved the menus into the codebase and out of the templates, we needed a way to allow 3rd party developers to insert their own menu items. We've introduced a new extension called UserMenu.
    The UserMenu extension contains the following methods:
    accountMenu
    This method allows you to add menu elements to the user menu. The method includes a parameter to allow you to add elements in one of 3 places within the menu: content, settings, or logout. mobileMenu
    Allows you to add elements to the mobile navigation menu. All elements are inserted before the "sign out" option. createMenu
    Replaces the CreateMenu extension, which has been deprecated. accountSettingsLinks
    Allows you to add links to the inner sidebar in the Account Settings overview tab (under the link for Notification Settings). Note: No, this does not add tabs to the Account Settings page, but fear not, that's coming. (Not in the UI Extension.)
    userNav
    This method will insert HTML in the user bar, to the left of the Create Menu. mobileNav
    Similar to the userNav method, but inserts HTML into the mobile navigation header.  
    What do you think of our changes to the Menus? Let us know in the comments below.
  8. Like
    Esther E. got a reaction from BomAle for an entry, IC5: Introduction to Listeners   
    I'm very excited to be posting my first blog entry for IPS, and thrilled that this is what I get to post.
    When we started planning the new development tools for v5, we looked at existing resources - from the marketplace, from some contributors, and from our own managed clients - and asked, what is it that developers find themselves doing the most often? Matt’s previous blog entry gave a brief summary of some of those functions. Today’s blog entry is going to focus on one of our powerful new tools, Listeners.
    One common reason for a code hook was to execute custom code when a specific action occurs. For example, when a topic is created, or when a new reply is posted. Listeners allow you to execute your code at key points in the workflow, without the worry of finding exactly the right hook location.
    Here is an excerpt from the BlogComment listener in our Cloud application:

     
    Creating Listeners
    Let’s start with the application’s Developer Center.

    We’ve added a new tab for Listeners. This tab lists all your existing listeners, as well as the class on which it is listening.
    You’ll notice that each listener can only observe a single class. While this may seem slightly tedious, the idea here was that as a developer, you should be conscious of what class you are working with. It also eliminates the need to check what type of object is being passed preventing accidents and unintended consequences when missing class checks.
    When you add a listener, there are several listener types available. We will discuss each one in detail.

     
    Content Listener
    This listener is fired on an Item or Comment (or Review) class. When adding a Content Listener, you must specify the class you are observing. When you hit Save, there is a validation check in place to ensure that the class exists, and that it is a valid class for the selected listener type.
    Content Listeners have the following methods available. All methods are included in the default listener file that is generated.
    onLoad
    Triggered when an object is loaded (in Content::constructFromData) NOTE: Be careful! This event could be fired at unexpected times. If you’re executing code here, you may want to check the dispatcher instance to verify that your code is running where you expect.
    onBeforeCreateOrEdit
    Fired when a form is submitted, before it is saved. This is the equivalent of Item::processBeforeCreate and Item::processBeforeEdit. This method includes a parameter to indicate whether this is a create or edit. onCreateOrEdit
    Fired after a form is saved. This is the equivalent of Item::processAfterCreate and Item::processAfterEdit. This method includes a parameter to indicate whether this is a create or edit. onDelete
    Fired after an object is deleted. onStatusChange
    Fired when any moderation action occurs. For example, when an item is pinned/unpinned, locked/unlocked. The moderation action is passed as a parameter so that your code can take that into account. onMerge
    Triggered AFTER an item is merged. onItemView
    Triggered when an item is viewed and the view count is incremented.  
    Invoice Listener
    An Invoice listener is fired only on the \IPS\nexus\Invoice class. This listener does not require you to specify a class to extend.
    The Invoice listener includes a single method, onStatusChange. This is fired when the invoice is saved and the status changes (pending/paid/expired/canceled).
     
    Member Listener
    The Member listener will be familiar to many of you. We have taken the MemberSync extension and moved it in its entirety to a listener, as it was a better fit for this structure. All the previous methods that were available are still available here. We have also added the following new methods:
    onJoinClub
    Fired when a member joins a club. If approval is required, this is fired after they are approved. onLeaveClub
    Fired when a member leaves or is removed from a club. onEventRsvp
    Fired when a member responds to an event. The response is included as a parameter to this method.  
    Commerce Package Listener
    The Commerce Package listener is fired on any implementation of \IPS\nexus\Invoice\Item. You must specify the class that you are observing.
    The methods are the same as those that are available in an item extension.
    onCancel onChange onDelete onExpireWarning onExpire onInvoiceCancel onPaid onPurchaseGenerated onReactivate onRenew onTransfer onUnpaid  
    Some Technical Notes
    All listener files will be generated in a “listeners” directory within your application. Your application’s data directory will include a listeners.json file that defines all your listeners. The json file is automatically generated when you add/remove a listener. All listener methods are optional. The default class that is generated will include all available methods, but they do not have to be present in your file. All listener methods are return type void. If a listener method fails, an exception will be thrown when IN_DEV is enabled. In production, the exception will be logged with a reference to the listener class and the event on which the exception occurred.  
    Firing Events
    Your code can fire any existing event using the Event::fire method.
    Example:
    Event::fire( 'onBeforeCreateOrEdit', $comment, array( $values ) ); The Event::fire method is called statically and accepts an event name, the object on which the event will be fired, and an array of additional parameters (varies according to the event that you are triggering).
    This concludes our introduction to Listeners. We do intend to implement additional listener types and events based on your feedback and as the need arises.
  9. Like
    Esther E. got a reaction from Marshall Slemp for an entry, IC5: Introduction to Listeners   
    I'm very excited to be posting my first blog entry for IPS, and thrilled that this is what I get to post.
    When we started planning the new development tools for v5, we looked at existing resources - from the marketplace, from some contributors, and from our own managed clients - and asked, what is it that developers find themselves doing the most often? Matt’s previous blog entry gave a brief summary of some of those functions. Today’s blog entry is going to focus on one of our powerful new tools, Listeners.
    One common reason for a code hook was to execute custom code when a specific action occurs. For example, when a topic is created, or when a new reply is posted. Listeners allow you to execute your code at key points in the workflow, without the worry of finding exactly the right hook location.
    Here is an excerpt from the BlogComment listener in our Cloud application:

     
    Creating Listeners
    Let’s start with the application’s Developer Center.

    We’ve added a new tab for Listeners. This tab lists all your existing listeners, as well as the class on which it is listening.
    You’ll notice that each listener can only observe a single class. While this may seem slightly tedious, the idea here was that as a developer, you should be conscious of what class you are working with. It also eliminates the need to check what type of object is being passed preventing accidents and unintended consequences when missing class checks.
    When you add a listener, there are several listener types available. We will discuss each one in detail.

     
    Content Listener
    This listener is fired on an Item or Comment (or Review) class. When adding a Content Listener, you must specify the class you are observing. When you hit Save, there is a validation check in place to ensure that the class exists, and that it is a valid class for the selected listener type.
    Content Listeners have the following methods available. All methods are included in the default listener file that is generated.
    onLoad
    Triggered when an object is loaded (in Content::constructFromData) NOTE: Be careful! This event could be fired at unexpected times. If you’re executing code here, you may want to check the dispatcher instance to verify that your code is running where you expect.
    onBeforeCreateOrEdit
    Fired when a form is submitted, before it is saved. This is the equivalent of Item::processBeforeCreate and Item::processBeforeEdit. This method includes a parameter to indicate whether this is a create or edit. onCreateOrEdit
    Fired after a form is saved. This is the equivalent of Item::processAfterCreate and Item::processAfterEdit. This method includes a parameter to indicate whether this is a create or edit. onDelete
    Fired after an object is deleted. onStatusChange
    Fired when any moderation action occurs. For example, when an item is pinned/unpinned, locked/unlocked. The moderation action is passed as a parameter so that your code can take that into account. onMerge
    Triggered AFTER an item is merged. onItemView
    Triggered when an item is viewed and the view count is incremented.  
    Invoice Listener
    An Invoice listener is fired only on the \IPS\nexus\Invoice class. This listener does not require you to specify a class to extend.
    The Invoice listener includes a single method, onStatusChange. This is fired when the invoice is saved and the status changes (pending/paid/expired/canceled).
     
    Member Listener
    The Member listener will be familiar to many of you. We have taken the MemberSync extension and moved it in its entirety to a listener, as it was a better fit for this structure. All the previous methods that were available are still available here. We have also added the following new methods:
    onJoinClub
    Fired when a member joins a club. If approval is required, this is fired after they are approved. onLeaveClub
    Fired when a member leaves or is removed from a club. onEventRsvp
    Fired when a member responds to an event. The response is included as a parameter to this method.  
    Commerce Package Listener
    The Commerce Package listener is fired on any implementation of \IPS\nexus\Invoice\Item. You must specify the class that you are observing.
    The methods are the same as those that are available in an item extension.
    onCancel onChange onDelete onExpireWarning onExpire onInvoiceCancel onPaid onPurchaseGenerated onReactivate onRenew onTransfer onUnpaid  
    Some Technical Notes
    All listener files will be generated in a “listeners” directory within your application. Your application’s data directory will include a listeners.json file that defines all your listeners. The json file is automatically generated when you add/remove a listener. All listener methods are optional. The default class that is generated will include all available methods, but they do not have to be present in your file. All listener methods are return type void. If a listener method fails, an exception will be thrown when IN_DEV is enabled. In production, the exception will be logged with a reference to the listener class and the event on which the exception occurred.  
    Firing Events
    Your code can fire any existing event using the Event::fire method.
    Example:
    Event::fire( 'onBeforeCreateOrEdit', $comment, array( $values ) ); The Event::fire method is called statically and accepts an event name, the object on which the event will be fired, and an array of additional parameters (varies according to the event that you are triggering).
    This concludes our introduction to Listeners. We do intend to implement additional listener types and events based on your feedback and as the need arises.
  10. Like
    Esther E. got a reaction from Miss_B for an entry, IC5: Introduction to Listeners   
    I'm very excited to be posting my first blog entry for IPS, and thrilled that this is what I get to post.
    When we started planning the new development tools for v5, we looked at existing resources - from the marketplace, from some contributors, and from our own managed clients - and asked, what is it that developers find themselves doing the most often? Matt’s previous blog entry gave a brief summary of some of those functions. Today’s blog entry is going to focus on one of our powerful new tools, Listeners.
    One common reason for a code hook was to execute custom code when a specific action occurs. For example, when a topic is created, or when a new reply is posted. Listeners allow you to execute your code at key points in the workflow, without the worry of finding exactly the right hook location.
    Here is an excerpt from the BlogComment listener in our Cloud application:

     
    Creating Listeners
    Let’s start with the application’s Developer Center.

    We’ve added a new tab for Listeners. This tab lists all your existing listeners, as well as the class on which it is listening.
    You’ll notice that each listener can only observe a single class. While this may seem slightly tedious, the idea here was that as a developer, you should be conscious of what class you are working with. It also eliminates the need to check what type of object is being passed preventing accidents and unintended consequences when missing class checks.
    When you add a listener, there are several listener types available. We will discuss each one in detail.

     
    Content Listener
    This listener is fired on an Item or Comment (or Review) class. When adding a Content Listener, you must specify the class you are observing. When you hit Save, there is a validation check in place to ensure that the class exists, and that it is a valid class for the selected listener type.
    Content Listeners have the following methods available. All methods are included in the default listener file that is generated.
    onLoad
    Triggered when an object is loaded (in Content::constructFromData) NOTE: Be careful! This event could be fired at unexpected times. If you’re executing code here, you may want to check the dispatcher instance to verify that your code is running where you expect.
    onBeforeCreateOrEdit
    Fired when a form is submitted, before it is saved. This is the equivalent of Item::processBeforeCreate and Item::processBeforeEdit. This method includes a parameter to indicate whether this is a create or edit. onCreateOrEdit
    Fired after a form is saved. This is the equivalent of Item::processAfterCreate and Item::processAfterEdit. This method includes a parameter to indicate whether this is a create or edit. onDelete
    Fired after an object is deleted. onStatusChange
    Fired when any moderation action occurs. For example, when an item is pinned/unpinned, locked/unlocked. The moderation action is passed as a parameter so that your code can take that into account. onMerge
    Triggered AFTER an item is merged. onItemView
    Triggered when an item is viewed and the view count is incremented.  
    Invoice Listener
    An Invoice listener is fired only on the \IPS\nexus\Invoice class. This listener does not require you to specify a class to extend.
    The Invoice listener includes a single method, onStatusChange. This is fired when the invoice is saved and the status changes (pending/paid/expired/canceled).
     
    Member Listener
    The Member listener will be familiar to many of you. We have taken the MemberSync extension and moved it in its entirety to a listener, as it was a better fit for this structure. All the previous methods that were available are still available here. We have also added the following new methods:
    onJoinClub
    Fired when a member joins a club. If approval is required, this is fired after they are approved. onLeaveClub
    Fired when a member leaves or is removed from a club. onEventRsvp
    Fired when a member responds to an event. The response is included as a parameter to this method.  
    Commerce Package Listener
    The Commerce Package listener is fired on any implementation of \IPS\nexus\Invoice\Item. You must specify the class that you are observing.
    The methods are the same as those that are available in an item extension.
    onCancel onChange onDelete onExpireWarning onExpire onInvoiceCancel onPaid onPurchaseGenerated onReactivate onRenew onTransfer onUnpaid  
    Some Technical Notes
    All listener files will be generated in a “listeners” directory within your application. Your application’s data directory will include a listeners.json file that defines all your listeners. The json file is automatically generated when you add/remove a listener. All listener methods are optional. The default class that is generated will include all available methods, but they do not have to be present in your file. All listener methods are return type void. If a listener method fails, an exception will be thrown when IN_DEV is enabled. In production, the exception will be logged with a reference to the listener class and the event on which the exception occurred.  
    Firing Events
    Your code can fire any existing event using the Event::fire method.
    Example:
    Event::fire( 'onBeforeCreateOrEdit', $comment, array( $values ) ); The Event::fire method is called statically and accepts an event name, the object on which the event will be fired, and an array of additional parameters (varies according to the event that you are triggering).
    This concludes our introduction to Listeners. We do intend to implement additional listener types and events based on your feedback and as the need arises.
  11. Like
    Esther E. got a reaction from Jim M for an entry, IC5: Introduction to Listeners   
    I'm very excited to be posting my first blog entry for IPS, and thrilled that this is what I get to post.
    When we started planning the new development tools for v5, we looked at existing resources - from the marketplace, from some contributors, and from our own managed clients - and asked, what is it that developers find themselves doing the most often? Matt’s previous blog entry gave a brief summary of some of those functions. Today’s blog entry is going to focus on one of our powerful new tools, Listeners.
    One common reason for a code hook was to execute custom code when a specific action occurs. For example, when a topic is created, or when a new reply is posted. Listeners allow you to execute your code at key points in the workflow, without the worry of finding exactly the right hook location.
    Here is an excerpt from the BlogComment listener in our Cloud application:

     
    Creating Listeners
    Let’s start with the application’s Developer Center.

    We’ve added a new tab for Listeners. This tab lists all your existing listeners, as well as the class on which it is listening.
    You’ll notice that each listener can only observe a single class. While this may seem slightly tedious, the idea here was that as a developer, you should be conscious of what class you are working with. It also eliminates the need to check what type of object is being passed preventing accidents and unintended consequences when missing class checks.
    When you add a listener, there are several listener types available. We will discuss each one in detail.

     
    Content Listener
    This listener is fired on an Item or Comment (or Review) class. When adding a Content Listener, you must specify the class you are observing. When you hit Save, there is a validation check in place to ensure that the class exists, and that it is a valid class for the selected listener type.
    Content Listeners have the following methods available. All methods are included in the default listener file that is generated.
    onLoad
    Triggered when an object is loaded (in Content::constructFromData) NOTE: Be careful! This event could be fired at unexpected times. If you’re executing code here, you may want to check the dispatcher instance to verify that your code is running where you expect.
    onBeforeCreateOrEdit
    Fired when a form is submitted, before it is saved. This is the equivalent of Item::processBeforeCreate and Item::processBeforeEdit. This method includes a parameter to indicate whether this is a create or edit. onCreateOrEdit
    Fired after a form is saved. This is the equivalent of Item::processAfterCreate and Item::processAfterEdit. This method includes a parameter to indicate whether this is a create or edit. onDelete
    Fired after an object is deleted. onStatusChange
    Fired when any moderation action occurs. For example, when an item is pinned/unpinned, locked/unlocked. The moderation action is passed as a parameter so that your code can take that into account. onMerge
    Triggered AFTER an item is merged. onItemView
    Triggered when an item is viewed and the view count is incremented.  
    Invoice Listener
    An Invoice listener is fired only on the \IPS\nexus\Invoice class. This listener does not require you to specify a class to extend.
    The Invoice listener includes a single method, onStatusChange. This is fired when the invoice is saved and the status changes (pending/paid/expired/canceled).
     
    Member Listener
    The Member listener will be familiar to many of you. We have taken the MemberSync extension and moved it in its entirety to a listener, as it was a better fit for this structure. All the previous methods that were available are still available here. We have also added the following new methods:
    onJoinClub
    Fired when a member joins a club. If approval is required, this is fired after they are approved. onLeaveClub
    Fired when a member leaves or is removed from a club. onEventRsvp
    Fired when a member responds to an event. The response is included as a parameter to this method.  
    Commerce Package Listener
    The Commerce Package listener is fired on any implementation of \IPS\nexus\Invoice\Item. You must specify the class that you are observing.
    The methods are the same as those that are available in an item extension.
    onCancel onChange onDelete onExpireWarning onExpire onInvoiceCancel onPaid onPurchaseGenerated onReactivate onRenew onTransfer onUnpaid  
    Some Technical Notes
    All listener files will be generated in a “listeners” directory within your application. Your application’s data directory will include a listeners.json file that defines all your listeners. The json file is automatically generated when you add/remove a listener. All listener methods are optional. The default class that is generated will include all available methods, but they do not have to be present in your file. All listener methods are return type void. If a listener method fails, an exception will be thrown when IN_DEV is enabled. In production, the exception will be logged with a reference to the listener class and the event on which the exception occurred.  
    Firing Events
    Your code can fire any existing event using the Event::fire method.
    Example:
    Event::fire( 'onBeforeCreateOrEdit', $comment, array( $values ) ); The Event::fire method is called statically and accepts an event name, the object on which the event will be fired, and an array of additional parameters (varies according to the event that you are triggering).
    This concludes our introduction to Listeners. We do intend to implement additional listener types and events based on your feedback and as the need arises.
  12. Like
    Esther E. got a reaction from Adriano Faria for an entry, IC5: Introduction to Listeners   
    I'm very excited to be posting my first blog entry for IPS, and thrilled that this is what I get to post.
    When we started planning the new development tools for v5, we looked at existing resources - from the marketplace, from some contributors, and from our own managed clients - and asked, what is it that developers find themselves doing the most often? Matt’s previous blog entry gave a brief summary of some of those functions. Today’s blog entry is going to focus on one of our powerful new tools, Listeners.
    One common reason for a code hook was to execute custom code when a specific action occurs. For example, when a topic is created, or when a new reply is posted. Listeners allow you to execute your code at key points in the workflow, without the worry of finding exactly the right hook location.
    Here is an excerpt from the BlogComment listener in our Cloud application:

     
    Creating Listeners
    Let’s start with the application’s Developer Center.

    We’ve added a new tab for Listeners. This tab lists all your existing listeners, as well as the class on which it is listening.
    You’ll notice that each listener can only observe a single class. While this may seem slightly tedious, the idea here was that as a developer, you should be conscious of what class you are working with. It also eliminates the need to check what type of object is being passed preventing accidents and unintended consequences when missing class checks.
    When you add a listener, there are several listener types available. We will discuss each one in detail.

     
    Content Listener
    This listener is fired on an Item or Comment (or Review) class. When adding a Content Listener, you must specify the class you are observing. When you hit Save, there is a validation check in place to ensure that the class exists, and that it is a valid class for the selected listener type.
    Content Listeners have the following methods available. All methods are included in the default listener file that is generated.
    onLoad
    Triggered when an object is loaded (in Content::constructFromData) NOTE: Be careful! This event could be fired at unexpected times. If you’re executing code here, you may want to check the dispatcher instance to verify that your code is running where you expect.
    onBeforeCreateOrEdit
    Fired when a form is submitted, before it is saved. This is the equivalent of Item::processBeforeCreate and Item::processBeforeEdit. This method includes a parameter to indicate whether this is a create or edit. onCreateOrEdit
    Fired after a form is saved. This is the equivalent of Item::processAfterCreate and Item::processAfterEdit. This method includes a parameter to indicate whether this is a create or edit. onDelete
    Fired after an object is deleted. onStatusChange
    Fired when any moderation action occurs. For example, when an item is pinned/unpinned, locked/unlocked. The moderation action is passed as a parameter so that your code can take that into account. onMerge
    Triggered AFTER an item is merged. onItemView
    Triggered when an item is viewed and the view count is incremented.  
    Invoice Listener
    An Invoice listener is fired only on the \IPS\nexus\Invoice class. This listener does not require you to specify a class to extend.
    The Invoice listener includes a single method, onStatusChange. This is fired when the invoice is saved and the status changes (pending/paid/expired/canceled).
     
    Member Listener
    The Member listener will be familiar to many of you. We have taken the MemberSync extension and moved it in its entirety to a listener, as it was a better fit for this structure. All the previous methods that were available are still available here. We have also added the following new methods:
    onJoinClub
    Fired when a member joins a club. If approval is required, this is fired after they are approved. onLeaveClub
    Fired when a member leaves or is removed from a club. onEventRsvp
    Fired when a member responds to an event. The response is included as a parameter to this method.  
    Commerce Package Listener
    The Commerce Package listener is fired on any implementation of \IPS\nexus\Invoice\Item. You must specify the class that you are observing.
    The methods are the same as those that are available in an item extension.
    onCancel onChange onDelete onExpireWarning onExpire onInvoiceCancel onPaid onPurchaseGenerated onReactivate onRenew onTransfer onUnpaid  
    Some Technical Notes
    All listener files will be generated in a “listeners” directory within your application. Your application’s data directory will include a listeners.json file that defines all your listeners. The json file is automatically generated when you add/remove a listener. All listener methods are optional. The default class that is generated will include all available methods, but they do not have to be present in your file. All listener methods are return type void. If a listener method fails, an exception will be thrown when IN_DEV is enabled. In production, the exception will be logged with a reference to the listener class and the event on which the exception occurred.  
    Firing Events
    Your code can fire any existing event using the Event::fire method.
    Example:
    Event::fire( 'onBeforeCreateOrEdit', $comment, array( $values ) ); The Event::fire method is called statically and accepts an event name, the object on which the event will be fired, and an array of additional parameters (varies according to the event that you are triggering).
    This concludes our introduction to Listeners. We do intend to implement additional listener types and events based on your feedback and as the need arises.
  13. Like
    Esther E. got a reaction from Marc Stridgen for an entry, IC5: Introduction to Listeners   
    I'm very excited to be posting my first blog entry for IPS, and thrilled that this is what I get to post.
    When we started planning the new development tools for v5, we looked at existing resources - from the marketplace, from some contributors, and from our own managed clients - and asked, what is it that developers find themselves doing the most often? Matt’s previous blog entry gave a brief summary of some of those functions. Today’s blog entry is going to focus on one of our powerful new tools, Listeners.
    One common reason for a code hook was to execute custom code when a specific action occurs. For example, when a topic is created, or when a new reply is posted. Listeners allow you to execute your code at key points in the workflow, without the worry of finding exactly the right hook location.
    Here is an excerpt from the BlogComment listener in our Cloud application:

     
    Creating Listeners
    Let’s start with the application’s Developer Center.

    We’ve added a new tab for Listeners. This tab lists all your existing listeners, as well as the class on which it is listening.
    You’ll notice that each listener can only observe a single class. While this may seem slightly tedious, the idea here was that as a developer, you should be conscious of what class you are working with. It also eliminates the need to check what type of object is being passed preventing accidents and unintended consequences when missing class checks.
    When you add a listener, there are several listener types available. We will discuss each one in detail.

     
    Content Listener
    This listener is fired on an Item or Comment (or Review) class. When adding a Content Listener, you must specify the class you are observing. When you hit Save, there is a validation check in place to ensure that the class exists, and that it is a valid class for the selected listener type.
    Content Listeners have the following methods available. All methods are included in the default listener file that is generated.
    onLoad
    Triggered when an object is loaded (in Content::constructFromData) NOTE: Be careful! This event could be fired at unexpected times. If you’re executing code here, you may want to check the dispatcher instance to verify that your code is running where you expect.
    onBeforeCreateOrEdit
    Fired when a form is submitted, before it is saved. This is the equivalent of Item::processBeforeCreate and Item::processBeforeEdit. This method includes a parameter to indicate whether this is a create or edit. onCreateOrEdit
    Fired after a form is saved. This is the equivalent of Item::processAfterCreate and Item::processAfterEdit. This method includes a parameter to indicate whether this is a create or edit. onDelete
    Fired after an object is deleted. onStatusChange
    Fired when any moderation action occurs. For example, when an item is pinned/unpinned, locked/unlocked. The moderation action is passed as a parameter so that your code can take that into account. onMerge
    Triggered AFTER an item is merged. onItemView
    Triggered when an item is viewed and the view count is incremented.  
    Invoice Listener
    An Invoice listener is fired only on the \IPS\nexus\Invoice class. This listener does not require you to specify a class to extend.
    The Invoice listener includes a single method, onStatusChange. This is fired when the invoice is saved and the status changes (pending/paid/expired/canceled).
     
    Member Listener
    The Member listener will be familiar to many of you. We have taken the MemberSync extension and moved it in its entirety to a listener, as it was a better fit for this structure. All the previous methods that were available are still available here. We have also added the following new methods:
    onJoinClub
    Fired when a member joins a club. If approval is required, this is fired after they are approved. onLeaveClub
    Fired when a member leaves or is removed from a club. onEventRsvp
    Fired when a member responds to an event. The response is included as a parameter to this method.  
    Commerce Package Listener
    The Commerce Package listener is fired on any implementation of \IPS\nexus\Invoice\Item. You must specify the class that you are observing.
    The methods are the same as those that are available in an item extension.
    onCancel onChange onDelete onExpireWarning onExpire onInvoiceCancel onPaid onPurchaseGenerated onReactivate onRenew onTransfer onUnpaid  
    Some Technical Notes
    All listener files will be generated in a “listeners” directory within your application. Your application’s data directory will include a listeners.json file that defines all your listeners. The json file is automatically generated when you add/remove a listener. All listener methods are optional. The default class that is generated will include all available methods, but they do not have to be present in your file. All listener methods are return type void. If a listener method fails, an exception will be thrown when IN_DEV is enabled. In production, the exception will be logged with a reference to the listener class and the event on which the exception occurred.  
    Firing Events
    Your code can fire any existing event using the Event::fire method.
    Example:
    Event::fire( 'onBeforeCreateOrEdit', $comment, array( $values ) ); The Event::fire method is called statically and accepts an event name, the object on which the event will be fired, and an array of additional parameters (varies according to the event that you are triggering).
    This concludes our introduction to Listeners. We do intend to implement additional listener types and events based on your feedback and as the need arises.
  14. Like
    Esther E. got a reaction from Grafidea for an entry, IC5: Introduction to Listeners   
    I'm very excited to be posting my first blog entry for IPS, and thrilled that this is what I get to post.
    When we started planning the new development tools for v5, we looked at existing resources - from the marketplace, from some contributors, and from our own managed clients - and asked, what is it that developers find themselves doing the most often? Matt’s previous blog entry gave a brief summary of some of those functions. Today’s blog entry is going to focus on one of our powerful new tools, Listeners.
    One common reason for a code hook was to execute custom code when a specific action occurs. For example, when a topic is created, or when a new reply is posted. Listeners allow you to execute your code at key points in the workflow, without the worry of finding exactly the right hook location.
    Here is an excerpt from the BlogComment listener in our Cloud application:

     
    Creating Listeners
    Let’s start with the application’s Developer Center.

    We’ve added a new tab for Listeners. This tab lists all your existing listeners, as well as the class on which it is listening.
    You’ll notice that each listener can only observe a single class. While this may seem slightly tedious, the idea here was that as a developer, you should be conscious of what class you are working with. It also eliminates the need to check what type of object is being passed preventing accidents and unintended consequences when missing class checks.
    When you add a listener, there are several listener types available. We will discuss each one in detail.

     
    Content Listener
    This listener is fired on an Item or Comment (or Review) class. When adding a Content Listener, you must specify the class you are observing. When you hit Save, there is a validation check in place to ensure that the class exists, and that it is a valid class for the selected listener type.
    Content Listeners have the following methods available. All methods are included in the default listener file that is generated.
    onLoad
    Triggered when an object is loaded (in Content::constructFromData) NOTE: Be careful! This event could be fired at unexpected times. If you’re executing code here, you may want to check the dispatcher instance to verify that your code is running where you expect.
    onBeforeCreateOrEdit
    Fired when a form is submitted, before it is saved. This is the equivalent of Item::processBeforeCreate and Item::processBeforeEdit. This method includes a parameter to indicate whether this is a create or edit. onCreateOrEdit
    Fired after a form is saved. This is the equivalent of Item::processAfterCreate and Item::processAfterEdit. This method includes a parameter to indicate whether this is a create or edit. onDelete
    Fired after an object is deleted. onStatusChange
    Fired when any moderation action occurs. For example, when an item is pinned/unpinned, locked/unlocked. The moderation action is passed as a parameter so that your code can take that into account. onMerge
    Triggered AFTER an item is merged. onItemView
    Triggered when an item is viewed and the view count is incremented.  
    Invoice Listener
    An Invoice listener is fired only on the \IPS\nexus\Invoice class. This listener does not require you to specify a class to extend.
    The Invoice listener includes a single method, onStatusChange. This is fired when the invoice is saved and the status changes (pending/paid/expired/canceled).
     
    Member Listener
    The Member listener will be familiar to many of you. We have taken the MemberSync extension and moved it in its entirety to a listener, as it was a better fit for this structure. All the previous methods that were available are still available here. We have also added the following new methods:
    onJoinClub
    Fired when a member joins a club. If approval is required, this is fired after they are approved. onLeaveClub
    Fired when a member leaves or is removed from a club. onEventRsvp
    Fired when a member responds to an event. The response is included as a parameter to this method.  
    Commerce Package Listener
    The Commerce Package listener is fired on any implementation of \IPS\nexus\Invoice\Item. You must specify the class that you are observing.
    The methods are the same as those that are available in an item extension.
    onCancel onChange onDelete onExpireWarning onExpire onInvoiceCancel onPaid onPurchaseGenerated onReactivate onRenew onTransfer onUnpaid  
    Some Technical Notes
    All listener files will be generated in a “listeners” directory within your application. Your application’s data directory will include a listeners.json file that defines all your listeners. The json file is automatically generated when you add/remove a listener. All listener methods are optional. The default class that is generated will include all available methods, but they do not have to be present in your file. All listener methods are return type void. If a listener method fails, an exception will be thrown when IN_DEV is enabled. In production, the exception will be logged with a reference to the listener class and the event on which the exception occurred.  
    Firing Events
    Your code can fire any existing event using the Event::fire method.
    Example:
    Event::fire( 'onBeforeCreateOrEdit', $comment, array( $values ) ); The Event::fire method is called statically and accepts an event name, the object on which the event will be fired, and an array of additional parameters (varies according to the event that you are triggering).
    This concludes our introduction to Listeners. We do intend to implement additional listener types and events based on your feedback and as the need arises.
  15. Like
    Esther E. got a reaction from SeNioR- for an entry, IC5: Introduction to Listeners   
    I'm very excited to be posting my first blog entry for IPS, and thrilled that this is what I get to post.
    When we started planning the new development tools for v5, we looked at existing resources - from the marketplace, from some contributors, and from our own managed clients - and asked, what is it that developers find themselves doing the most often? Matt’s previous blog entry gave a brief summary of some of those functions. Today’s blog entry is going to focus on one of our powerful new tools, Listeners.
    One common reason for a code hook was to execute custom code when a specific action occurs. For example, when a topic is created, or when a new reply is posted. Listeners allow you to execute your code at key points in the workflow, without the worry of finding exactly the right hook location.
    Here is an excerpt from the BlogComment listener in our Cloud application:

     
    Creating Listeners
    Let’s start with the application’s Developer Center.

    We’ve added a new tab for Listeners. This tab lists all your existing listeners, as well as the class on which it is listening.
    You’ll notice that each listener can only observe a single class. While this may seem slightly tedious, the idea here was that as a developer, you should be conscious of what class you are working with. It also eliminates the need to check what type of object is being passed preventing accidents and unintended consequences when missing class checks.
    When you add a listener, there are several listener types available. We will discuss each one in detail.

     
    Content Listener
    This listener is fired on an Item or Comment (or Review) class. When adding a Content Listener, you must specify the class you are observing. When you hit Save, there is a validation check in place to ensure that the class exists, and that it is a valid class for the selected listener type.
    Content Listeners have the following methods available. All methods are included in the default listener file that is generated.
    onLoad
    Triggered when an object is loaded (in Content::constructFromData) NOTE: Be careful! This event could be fired at unexpected times. If you’re executing code here, you may want to check the dispatcher instance to verify that your code is running where you expect.
    onBeforeCreateOrEdit
    Fired when a form is submitted, before it is saved. This is the equivalent of Item::processBeforeCreate and Item::processBeforeEdit. This method includes a parameter to indicate whether this is a create or edit. onCreateOrEdit
    Fired after a form is saved. This is the equivalent of Item::processAfterCreate and Item::processAfterEdit. This method includes a parameter to indicate whether this is a create or edit. onDelete
    Fired after an object is deleted. onStatusChange
    Fired when any moderation action occurs. For example, when an item is pinned/unpinned, locked/unlocked. The moderation action is passed as a parameter so that your code can take that into account. onMerge
    Triggered AFTER an item is merged. onItemView
    Triggered when an item is viewed and the view count is incremented.  
    Invoice Listener
    An Invoice listener is fired only on the \IPS\nexus\Invoice class. This listener does not require you to specify a class to extend.
    The Invoice listener includes a single method, onStatusChange. This is fired when the invoice is saved and the status changes (pending/paid/expired/canceled).
     
    Member Listener
    The Member listener will be familiar to many of you. We have taken the MemberSync extension and moved it in its entirety to a listener, as it was a better fit for this structure. All the previous methods that were available are still available here. We have also added the following new methods:
    onJoinClub
    Fired when a member joins a club. If approval is required, this is fired after they are approved. onLeaveClub
    Fired when a member leaves or is removed from a club. onEventRsvp
    Fired when a member responds to an event. The response is included as a parameter to this method.  
    Commerce Package Listener
    The Commerce Package listener is fired on any implementation of \IPS\nexus\Invoice\Item. You must specify the class that you are observing.
    The methods are the same as those that are available in an item extension.
    onCancel onChange onDelete onExpireWarning onExpire onInvoiceCancel onPaid onPurchaseGenerated onReactivate onRenew onTransfer onUnpaid  
    Some Technical Notes
    All listener files will be generated in a “listeners” directory within your application. Your application’s data directory will include a listeners.json file that defines all your listeners. The json file is automatically generated when you add/remove a listener. All listener methods are optional. The default class that is generated will include all available methods, but they do not have to be present in your file. All listener methods are return type void. If a listener method fails, an exception will be thrown when IN_DEV is enabled. In production, the exception will be logged with a reference to the listener class and the event on which the exception occurred.  
    Firing Events
    Your code can fire any existing event using the Event::fire method.
    Example:
    Event::fire( 'onBeforeCreateOrEdit', $comment, array( $values ) ); The Event::fire method is called statically and accepts an event name, the object on which the event will be fired, and an array of additional parameters (varies according to the event that you are triggering).
    This concludes our introduction to Listeners. We do intend to implement additional listener types and events based on your feedback and as the need arises.
  16. Like
    Esther E. reacted to Matt for an entry, Introducing Invision Community 5's development tools   
    When planning Invision Community 5, we knew we had a unique opportunity to hit the reset button.
    It's hard to believe, but how we work with the framework has been the same since 2013. The priorities we held a decade ago no longer align with our current vision.
    The landscape of modern frameworks has evolved, and we have adapted accordingly.
    When we initially designed the Invision Community 4 framework all those years ago, our goal was to create an open development platform. We aimed to empower you with the ability to extend virtually every function we built, granting complete freedom to shape our UI, workflows, and functionality according to your needs.
    However, over the decade, we have realised that this freedom has inadvertently become a prison. While monkey patching allowed the overloading of any method we had written, it came at a significant cost. Even the slightest change to our original method signatures could break an entire community, forcing you to update your applications.
    Ironically, the more we strived to enhance our framework, the less stable your applications became.
    We dismissed requests for proper workflows like extensions, webhooks, and event listeners, urging you to rely solely on method overloading, often having to copy chunks of code just to add a few lines of your own.
    Invision Community 5 represents a much-needed reset. It is a moment to reassess everything we know about extending frameworks and construct definitive pathways and workflows that serve you better.
    Our aim is for you to continue crafting exceptional applications that bring fresh functionality to the Invision Community while protecting the integrity of our core functionality.
    Change can be intimidating, but let us embrace this opportunity to bid farewell to outdated and fragile tools and pave the way for a collaborative future with precision-designed resources instead.
    In the coming weeks, we will delve deeply into the new development tools offered by Invision Community 5, but today, I wanted to give you a brief overview of what's to come for code development. We will do a near-future entry on creating themes with Invision Community 5.
    Out with the old
    Monkey patching via code hooks was the primary way to add functionality to Invision Community. However, it meant that we could not update our code base without risking breaking many popular applications, a situation that will only get worse as PHP starts to lock down type hinting and casting.
    Furthermore, IDEs have a tough time working out relationships between classes requiring special tools to create alias files to allow full use of many advanced tools of PHPStorm and other editors. Finally, monkey patching relied on heavy use of eval() which is not a very efficient PHP method.
    Monkey patching has been removed in Invision Community 5.
    Your IDE with Invision Community 4.

    Your IDE with Invision Community 5.

     
    Template hooks, as you know them, have also been removed. I only mention this now as they often go hand-in-hand with code hooks to add items to menus, data-attributes to blocks and CSS classes to many areas. We will do a more thorough blog on theme editing soon, but we still retain a way to hook into key areas.
    In with the new
    We will write a series of developer blogs to look in more detail at the new systems, but here is a quick overview of the new tools and a brief description of what they are capable of.
    Content Menus
    We have removed much of the menu logic for content items and comments from templates and created a simple, extensible way to add links to commonly used menus, such as the item moderation menu and the per-post 'three dots' menu. You no longer need a template hook to perform such a basic task.

    UIExtension
    This framework provides you with an easy way to add CSS, data-attributes and even templated HTML into specific areas within nodes and content items/comments. A common reason to create template hooks was to tweak the CSS classes or add data-attributes for client-side scripting.

    In the same way that we've stripped menus out of templates, we've removed the content item badges (pinned, hidden, etc.) and into the UIExtension making it easy to add your own badges to this area without the need for template or code hooks.
    Finally, UIExtension can add form fields into Invision Community content forms.
    Event Listeners
    The easiest way to describe this is like MemberSync but for content (items and comments), members, Commerce invoices and Commerce packages.
    Do you need to run your code each time a forum post is made? Not a problem. Do you need to run your code when a topic is locked? Perhaps when an item is viewed? All this and more is possible via the new listeners.
    The advantage is that you don't need to copy chunks of our code when overloading a single method in a hook. You write your clean code in an object-specific listener.

    What else?
    We have also undertaken a long overdue code clean-up. Every single file has been updated to update return types and function signature types and to use aliases instead of fully qualified names (for example, new Url() instead of new \IPS\Http\Url()).
    Many methods have been removed from the Content classes and moved into traits. Likewise, many interfaces are now traits to reuse code properly, reducing the behemoth-sized classes.
    Elsewhere, all the search indexing logic that was entwined in content and node classes has now been moved to the extension system, further reducing the noise and volume of key classes.
    A Quick Recap
    Generic broad reaching tools such as monkey patching and template overloading have been removed. New precision tools with a specific use-case have been created. There are less opportunities to overwrite and change our UI and functionality but easier ways to extend and create new functionality. The future
    We have worked hard on these development tools to ensure that most of what you do now can be achieved in Invision Community 5. We have had input from various stakeholders, including those who regularly create modifications for Invision Community to ensure our new tools are fit for purpose.
    Through the alpha and beta testing phase, we want your feedback, and we will listen to your suggestions. We cannot promise that you can do everything with Invision Community 5 that you currently do, which is intentional, but we are confident that you can still hit all the major beats. We want to protect our UI and functionality a little more but encourage you to build amazing new functionality to work alongside Invision Community 5.
    Together, we will shape the next era of Invision Community.
×
×
  • Create New...