Jump to content


  • Posts

  • Joined

  • Last visited

  • Days Won


 Content Type 


Release Notes

IPS4 Guides

IPS4 Developer Documentation

Invision Community Blog

Development Blog

Deprecation Tracker

Providers Directory





Everything posted by Rikki

  1. Description The menu widget adds interactivity to menu elements created with HTML & CSS. Menus present a list of options for the user to choose from as a popup 'attached' to the trigger element. Menus can optionally allow users to check one or more of the options. Usage The menu widget does not build the menu element itself; it should exist in the page as an HTML element with the appropriate CSS classes already added to it (a decision made so that menus can be accessible even without javascript enabled). The attributes to initialize the menu should be added to the trigger element - that is, the element a user will click to show the menu. Typically this would be a link or button. The menu element should have the same ID as the trigger element, but suffixed with _menu; e.g. if the trigger has the ID 'trigger', the ID on the menu element should be 'trigger_menu'. With these conditions met, initializing the menu simply consists of adding the widget attribute to the trigger. <!-- The trigger --> <a href='#elMyMenu_menu' id='elMyMenu' data-ipsMenu>Open Menu</a> <!-- The menu --> <ul id='elMyMenu_menu' class='ipsMenu'> ... </ul> Pinging URLs from menu items The menu widget provides a mechanism for pinging the menu item URL via AJAX instead of following the link directly. To use this behavior, provide the data-action data attribute on the menu list item: <li class='ipsMenu_item' data-action='ipsMenu_ping'><a href='...'>Menu item</a></li> Providing menu values By including the attribute data-ipsMenuValue on your menu item, you can provide a value which will be passed by events, and can be used by event handlers to identify the menu item: <li class='ipsMenu_item' data-ipsMenuValue='itemID'><a href='...'>Menu item</a></li> Options className (String; optional; default 'ipsMenu') Provides the base menu classname that the widget will use. By default this is the standard ipsMenu class, but by passing a different classname, alternative menu styles can be built. closeOnClick (Boolean; optional; default true) Determines whether the menu element will close when an item is clicked. closeOnBlur (Boolean; optional; default true) Determines whether the menu element will close when somewhere other than the menu is clicked or focused (e.g. the document). appendTo (Selector; optional) By default, the menu element is moved and appended to the global container element (body, usually). By using this option, you can control which element the menu will be inserted into - useful when a menu is being created inside a popup for example. activeClass (String; optional) Specifies a classname that will be applied to the trigger element when the menu is opened. selectable (Mixed; optional; default false) Specifies whether the menu will have 'selectable' functionality, allowing items to be checked and unchecked. If the value passed is radio, only one item will be selectable at once. If value passed is checkbox, multiple items can be selected. Note that the menu must have the correct CSS classes already for selectable functionality to work. withStem (Boolean; optional; default true) Determines whether the menu will have a stem to indicate its relationship to the trigger. stemOffset (Number; optional; default 15) A value that indicates the offset of the stem, i.e. the distance from the edge to the center point of the stem. Should rarely need to be set unless the CSS classes are changed significantly. stopLinks (Boolean; optional; default false) Stops menu item links from being followed by the browser by calling preventDefault on the event. above (Boolean; optional; default false) By default, menus will prefer to open below the trigger. Setting this option to true will cause this menu to prefer being above the trigger instead. Events emitted by ips.ui.menu Events are emitted on the trigger element. menuOpened Triggered when the user opens a menu by clicking on the trigger Event data: elemID The ID of the trigger element this menu belongs to originalEvent The original event object from the click (useful for calling originalEvent.preventDefault() to prevent the default browser action) menu Reference to the menu element menuClosed Triggered when the menu closes Event data: elemID The ID of the trigger element this menu belongs to menuPingSuccessful Triggered when the menu closes No event data. menuItemSelected Triggered when the user selects an item within the menu Event data: triggerElem Reference to the trigger element triggerID ID of the trigger element menuElem Reference to the menu element originalEvent The raw javascript event object from the click selectedItemID String containing the value of the selected menu item, if it provided a data-ipsMenuValue attribute. selectedItems Object containing references to checked items, when menu is selectable Events to which ips.ui.menu responds closeMenu Can be fired on the menu or trigger element elements to close the menu.
  2. Description The Lightbox widget implements a commonly-seen interface convention whereby images can be clicked to view them at a larger size inside a popup that appears on the page. In addition, related images are grouped so that they can be scrolled through inside the popup. IPS4's lightbox also supports built-in commenting. Example @todo Usage A lightbox can be initialized on a single image as simply as: <img src='http://url/to/image' data-ipsLightbox> However, it's likely additional functionality would be desired. A common practice is to link to a large image but display a small thumb image on the page. In this situation, the lightbox is initialized on the link: <a href='http://url/to/image' data-ipsLightbox><img src='http://url/to/thumb'></a> In both of the above example, a single image will be displayed in the lightbox. It's also possible to group images together - such as in a photo album, or a group of images for a single content item. In this case, you should initialize a lightbox on all of the items, and include the group option which should be consistent between them: <a href='http://url/to/image1' data-ipsLightbox data-ipsLightbox-group='example'><img src='http://url/to/thumb1'></a> <a href='http://url/to/image2' data-ipsLightbox data-ipsLightbox-group='example'><img src='http://url/to/thumb2'></a> <a href='http://url/to/image3' data-ipsLightbox data-ipsLightbox-group='example'><img src='http://url/to/thumb3'></a> Options group (String; optional) If provided, the widget will display other images from the same group in the lightbox. fullURL (String; optional) By default, the full size image will be loaded from the href attribute on the a tag. If this option is provided, the full size image will instead be loaded from this URL. className (String; optional; default ipsLightbox) Allows you to provide a classname prefix which will be used when the lightbox UI is built, enabling a custom UI to be shown. preload (Boolean; optional; default true) Determines whether images in the lightbox should be preloaded when the lightbox is opened, designed to reduce wait time for the user as they scroll through images. Events emitted by ips.ui.lightbox lightboxImageShown Triggered when the lightbox shows a new image. Event data: image elem Reference to this image's widget element (may different from <em>imageElem</em> if a link launches the lightbox) imageElem Reference to the actual image element largeImage URL to the fullsize image originalImage URL to the image that already existed on the page (e.g. a thumbnail) meta The meta information for the image, if any triggerElem Reference to the widget element that originally opened the lightbox, which may or may not be the image being viewed.
  3. Description Infinite scrolling is a technique whereby new content is loaded into the page automatically when the user scrolls near the bottom of the page or section. It's a technique commonly associated with sites like Facebook and Twitter. Note: Infinite scrolling can present user interface challenges if not used wisely, including not being able to reach links at the bottom of the page. Before choosing to use infinite scrolling, consider the usability impacts it may have. It may be more appropriate to display a button at the bottom of your content inviting a user to load more content manually. In order to progressively enhance the page, and ensure users with javascript disabled can still access content, infinite scrolling in IPS4 works with the standard pagination markup. Usage The infinite scroll widget works by having the content inside of a scroll scope element. The element has a fixed height, and hides its overflow so that it scrolls. The content is placed inside a container element and is an arbitrary length, and as the user scrolls and the end of the container approaches the bottom of the scroll scope, the widget will load more content to display. A standard pagination control should exist inside the scroll scope element; it can be hidden from javascript-enabled browsers by placing it inside a noscript tag. Options scrollScope (Selector; optional; default 'body') A selector that idenfities the element which will be the scroll scope; that is, the element that has a fixed height and which the user will scroll to show more content. container (Selector; required) A selector identifying the container element inside which your content should exist. distance (Mixed; optional; default 50) A value that determines how close to the bottom of the scrollScope the container must be to trigger the loading of new content. The value can either be a number (e.g. 50) or a percentage (e.g. 10%) which represents a percentage height of the scrollScope element. url (String; required) The URL that is called to retrieve more content from the server. pageParam (String; optional; default 'page') The parameter used to pass the page value when retrieving more content from the server. loadingTpl (Template key; optional; default 'core.infScroll.loading') The template used to render the 'loading' text when the widget is currently loading new content. pageBreakTpl (Template key; optional; default 'core.infScroll.pageBreak') The template used to render the breaks between each 'page' of content as it is inserted into the container to aid the user experience. disableIn (String; optional; default 'phone') Specifies which responsive views, if any, the infinite scroll behavior is disable in. Value should be one or more registered breakpoint keys (phone, tablet or desktop), comma delimited. Events emitted by ips.ui.infScroll infScrollPageLoaded Triggered when a new page of content has been loaded. Event data: page The number of the most current page (i.e. the one just loaded).
  4. Description The hovercard widget can be used to display a popup with remote content, which is attached to a trigger element. Example @todo Usage The hovercard widget is initialized by including the widget key attribute on a link: <a href='...' data-ipsHover>Hover on me</a> The URL that the link points to is also the URL that is called to fetch the hovercard content, by default. Options target (String; optional) If a URL other than the link's href should be used to load the hovercard content, it can be specified in this option. content (Mixed; optional) Sets the content of the hovercard. If a selector is provided, the contents of the matching element will be inserted into to hovercard. Alternatively, a string can be supplied which will be inserted into the hovercard. Using this option means remote content won't be loaded by the hovercard. timeout (Number; optional; default 0.75) The number of seconds between the user hovering on the element and the hovercard beginning to load (and conversely, hiding when the mouse leaves the element). attach (Selector; optional) The element that the hovercard will be attached to. If not specified, the element that the widget is initialized on will be used. width (Number; optional; default 300) The width in pixels at which the hovercard will display. onClick (Boolean; optional; default false) If true, the hovercard will be activated only when the user clicks on the element. By default, the hovercard will show on hover.
  5. Description The flashMsg widget can be used to display a 'flash message' to the user - a short notification that appears on the screen for a few seconds, before disappearing. It is commonly used for 'success' messages when an action occurs. Example @todo Usage A flash message can be created with the data API, as follows: <span data-ipsFlashMsg data-ipsFlashMsg-text='My message'> The element used with this method is not important; the widget will not interact with the element in any way. However, in most cases, flash messages will be created on the fly in response to user actions, so the more common use will be calling them programatically. This can be done in javascript modules with the following code: ips.ui.flashMsg.show('My message'); Flash messages can be automatically created in two additional ways: Cookies - set a cookie with the key flmsg. The cookie will be deleted after the message displays. URL - pass the parameter flmsg in the page URL, with the value being the message to show. The value should be URL encoded. Options text (String; required) The text to display in the flash message. A sentence or two maximum is recommended. extraClasses (String; optional; default 'ipsPad') A space-separated string of extra CSS classnames that will be applied to the flash message box. timeout (Number; optional; default 2) The number of seconds for which this flash will be shown before hiding itself. Events closeFlashMsg.flashMsg Hides the currently-shown flash message. If any messages are queued, the next will be shown automatically.
  6. Description The dialog widget displays popup windows within the page, typically loading content from a remote source. Usage A dialog is defined simply by including the widget key as an attribute on a trigger element. It is recommended that the trigger element be a link or button, so that if the user has javascript disabled their browser can take them to a full-page version of the dialog contents. <a href='...' data-ipsDialog data-ipsDialog-url='...'>Launch dialog</a> Dialogs offer special functionality for forms contained with the dialog, enabling them to be automatically validated and submitted via AJAX when used with the form helper from the IPS4 PHP framework. See the remoteVerify and remoteSubmit options below. Obtaining a dialog reference If you need to control a dialog programmatically, you can do so by first obtaining the reference to the dialog. To do so, call the getObj method, passing in the element on which the dialog was created as the parameter: // HTML <div id='elementWithDialog' data-ipsDialog> ... </div> // Javascript var dialog = ips.ui.dialog.getObj( $('#elementWithDialog') ); The returned reference to the dialog instance can be used to call the methods shown below. Creating a dialog programmatically Dialogs can be created programmatically by controllers instead of being created on particular elements. To achieve this, call the create method: var dialog = ips.ui.dialog.create( object options ); The options parameter should be an object containing keys for the options below. This method returns a reference to the dialog instance on which you can call methods to control the dialog, for example: dialog.show(); dialog.hide(); Instance methods show( boolean initOnly ) Initializes and shows the dialog. If initOnly is true, the dialog is initialized but not immediately shown. hide() Hides the dialog. remove( boolean hideFirst ) Removes the dialog from the DOM. If hideFirst is true, the dialog will call hide() and remove itself after the animation is completed. setLoading( boolean loading ) If loading is true, indicates to the user that something is being loaded (i.e. shows a spinner in the dialog). If loading is false, removes the loading state. Note: this method doesn't empty the dialog content first. Call instance.updateContent('') manually if desired. updateContent( string newContent ) Updates the contents of the dialog with the provided newContent. New controllers/widgets are automatically initialized after updating. Options url (String; optional) If provided, the dialog contents will be loaded by calling this URL when the dialog is launched. content (Selector; optional) If the dialog content already exists in the DOM, this option should be a selector that will locate the wrapper element for the content. modal (Boolean; optional; default true) If true, the page background will fade, preventing the user from interacting with it until the dialog is dismissed. title (String; optional) Sets the title shown in the dialog window. If not set, no title bar will display. size (String; optional) If provided, will set a special class on the dialog designed to change its size. Currently, narrow, medium, wide and fullscreen are supported as values for this option. If not provided, the default dialog width will be used (defined in CSS). close (Boolean; optional; default true) Determines whether the dialog should be built with a close button. fixed (Boolean; optional; default false) Determines whether the dialog is fixed. A fixed dialog is anchored to the viewport, not the page. Its height will also be fixed, with the content scrolling inside if necessary. If this is false, the dialog will scroll with the page, and it will expand to fit all the content being shown, regardless of length. remoteVerify (Boolean; optional; default true) When the dialog contains a form built using the IPS4 PHP framework form helper, this option instructs the dialog to automatically validate the form values and present any errors to the user. remoteSubmit (Boolean; optional; default false) When the dialog contains a form built using the IPS4 PHP framework form helper, this option instructs the dialog to submit the form values via AJAX. If remoteVerify is also true, two separate AJAX requests are fired; first, the form is validated, and if successful the form is submitted with a second request. If remoteSubmit is true, after a successful submit the dialog is closed automatically. If the flashMessage option is provided, it will be shown at this point. By default, remoteSubmit is false, which means forms submit via a traditional page reload. callback (Function; optional) Specifies a callback function that will be executed after the dialog has loaded remote content. Note: this option cannot be provided via the data API, and is therefore only available when interacting with ips.ui.dialog directly. forceReload (Boolean; optional; default false) By default, once a dialog has loaded its content, it will not do so again even if the dialog is relaunched. By setting this option to true, the dialog will call the url every time it is opened. This option only takes effect when the url option is used. Events emitted by ips.ui.dialog openDialog Triggered when the dialog is shown. Event data: elemID ID of the element that triggered the dialog dialogID ID of the dialog element dialog Reference to the dialog element contentLoaded Boolean indicating whether the dialog content has been loaded (may be false if remote content is used) hideDialog Triggered when the dialog is hidden. Event data: elemID ID of the element that triggered the dialog dialogID ID of the dialog element dialog Reference to the dialog element dialogContentLoaded Triggered after remote content has been loaded and inserted into the dialog. Event data: elemID ID of the element that triggered the dialog dialogID ID of the dialog element dialog Reference to the dialog element contentLoaded Boolean indicating whether the dialog content has been loaded (always true here) Events to which ips.ui.dialog responds closeDialog Instructs the dialog to close. Event data: dialogID (required) The ID of the dialog to close (the event is ignored if the ID does not match the current dialog)
  7. Description The Chart widget takes a specially-formatted table of data, and uses Google Charts to render it as an attractive chart. The Chart widget is designed for use soley with the output of the \IPS\Helpers\Chart class and is documented here for completeness only. Consult the documentation for the PHP class for more information. Options type (String; required) Specifies the type of chart to render, e.g. LineChart extraOptions (Object; optional) Object of extra options passed through to the Google Charts library. Events chartInitialized Triggered after the chart has been initialized.
  8. Description The Captcha widget is designed to allow captcha to be used dynamically, including situations where it is initialized after the page has loaded (e.g. in popups). The necessary javascript libraries from providers are loaded on the fly. The widget presently supports reCaptcha and KeyCaptcha. Usage The widget is initialized as follows: <div data-ipsCaptcha data-ipsCaptcha-service='recaptcha' data-ipsCaptcha-key='...'> </div> Options service (String; required) Specifies the captcha service to initialize. Either recaptcha or keycaptcha. key (recaptcha only) (String; required) Specifies the reCaptcha API key to use. Not used for KeyCaptcha. lang (recaptcha only) (String; optional; default 'en-US') Specifies the language to load for reCaptcha. Not used for KeyCaptcha. theme (recaptcha only) (String; optional; default 'white') Specifies the theme to load for reCaptcha. Not used for KeyCaptcha. Events The Captcha widget does not emit any events.
  9. Description The autocomplete widget adds two groups of functionality to a text input. Firstly, it adds support for suggestions based on what the user is typing, and secondly, it adds support for tokenization, allowing the user to enter multiple distinct values into one field. This functionality is useful for entering tags, for example. Example @todo Usage The autocomplete widget should be initialized on a text input with the correct widget key attribute: <input type='text' placeholder='Enter text to search' name='field_name' data-ipsAutocomplete data-ipsAutocomplete-dataSource='...'> Obtaining an autoComplete reference If you need to control an autoComplete programmatically, you can do so by first obtaining a reference to the instance. To do so, call the getObj method, passing in the element on which the dialog was created as the parameter: // HTML <input type='text' id='inputWithAutocomplete' data-ipsAutocomplete> // Javascript var autocomplete = ips.ui.autocomplete.getObj( $('#inputWithAutocomplete') ); You can then call the methods below on the returned reference. Methods addToken( string value ) Returns boolean; true on success. Adds a token to the autocomplete with the provided value. getTokens() Returns array containing values. Fetches all of the token values currently present in the autocomplete. removeToken( element token ) Returns void. Removes the given token element from the autocomplete. Note that this method expects the DOM/jQuery token element, not the value. removeAll() Returns void. Removes all tokens from the autocomplete. Options dataSource (Mixed; optional) Specifies the data source that the autocomplete widget should use for suggestions. Possible values: URL - when a URL is supplied, the widget will fetch results from this address with the current value as the parameter q, expecting a JSON object in response Selector - which locates a datalist element on the page, the options of which will provides the results None - initializes the autocomplete widget with no dataSource, meaning suggestions will not be offered to the user maxItems (Number; optional) Specifies the maximum number of tokens the user can enter into the field. When the maximum is reached, no further tokens can be added. minLength (Number; optional) Specifies the minimum length a value must be, when freeChoice is true. maxLength (Number; optional) Specifies the maximum length a value can be, when freeChoice is true. unique (Boolean; optional; default false) Determines whether tokens entered must be unique. If true, a duplicate will not be permitted and a tooltip will inform the user why. In addition, when a token is used it is automatically removed from the list of suggestions offered for further tokens. freeChoice (Boolean; optional; default true) If true, the user will be able to enter their own values. If false, they will only be able to select values from the suggestion list. e.g. @todo forceLower (Boolean; optional; default false) Forces all tokens entered to be lowercase. resultsElem (Selector; optional) By default, when suggestions are presented they will appear in a floating list attached to the text field. If a selector is provided in this option, the results will instead be inserted into the element it locates. queryParam (String; optional; default 'q') If a URL is used for dataSource, this parameter provides the parameter name used for the lookup e.g. index.php?q=<term>. addTokenText (Template key; optional; default 'core.autocomplete.addToken') If freeChoice is false, this option specifies the template that will be used to build the link that the user will click to display the suggestions list. fieldTemplate (Template key; optional; default 'core.autocomplete.field') The key to the template that will be used to build the widget wrapper (into which the original text field is inserted). resultsTemplate (Template key; optional; default 'core.autocomplete.resultItem') The key to the template that will be used to build an item within the suggestions list. For example, in a list for selecting colors, the template might show a swatch alongside the name. tokenTemplate (Template key; optional; default 'core.autocomplete.token') The key to the template that will be used to render an individual token when it is created in the field. Events Events are emitted on the original text field. autoCompleteReady Triggered when the autocomplete widget is ready for interaction (i.e. the wrapper is built and existing values have been constructed and inserted). Event data: elemID ID of the element the widget was initialized on elem Reference to the above element currentValues Array of current values in the widget (in this case, values that already existed when the widget was initialized) tokenAdded Triggered when a token is created in the text field, either by selecting a value or hitting the delimiter character (comma by default) Event data: token The value of the token that has been added tokenList Array of all current token values totalTokens Count of the number of tokens added html The HTML representing this item in the results list tokenDeleted Triggered when a token is deleted Event data: token The value of the token that has been deleted tokenList Array of all current token values totalTokens Count of the number of tokens added
  10. Description The autoCheck widget makes it simple to select checkboxes of a certain type on a page. This is useful for moderation tools, for example - simply by specifying a filter, topics that match that filter can be selected. Note: The autoCheck widget works in conjunction with the ips.ui.menu widget. It will not work on other kinds of elements. Example @todo Usage The widget itself is initialized on the menu trigger element: <!-- The trigger --> <a href='#elMyMenu_menu' id='elMyMenu' data-ipsMenu data-ipsAutoCheck data-ipsAutoCheck-context='#elContext'> Open Menu </a> Each menu option then receives the special data attribute data-ipsMenuValue (as described in the menu widget documentation) which contains the filter type that the option will check: <li class='ipsMenu_item' data-ipsMenuValue='unapproved'> <a href='...'>Has Unapproved</a> </li> And finally, each checkbox within the context element (see options below) can receive another special data attribute data-state, to indicate which filters it matches: <input type='checkbox' data-state='unread unapproved'> Multiple filter values are simply separated by a space. Options context (Selector; required) A CSS selector which indicates the wrapper element that contains all of the checkboxes which will be checked for filter matches. For example, if a Data List structure is used, this option might be set to the ID of the root list element. Events Events are emitted on the menu trigger element. autoChecked Triggered when the user selects an option that has a data-ipsMenuValue attribute. Event data: menu Reference to the menu trigger element currentFilter The filter that has been selected count The number of checkboxes that matched
  11. Description The alert widget is designed to replace the built-in javascript alert, providing a more pleasant user experience as well as more flexible options and built in callbacks. Several other types of alert dialogs are supported too. Usage Unlike most UI widgets, the alert widget is instantiated programatically, rather than using the data API - typically as a response to a user action in a controller, for example. An alert is created as follows: ips.ui.alert.show( { type: 'alert', icon: 'warning', message: 'Alert message', subText: 'Message subtext', callbacks: { ok: function () { // Do something } } }); Each type of alert shows different buttons depending on the use; a callback can be assigned to each button shown for that type. Alert types Alert Shows a message, with a single button to dismiss the alert. Type: alert Available button callbacks: ok Confirm Confirm shows a message with OK and Cancel buttons. Type: confirm Available button callbacks: ok, cancel Prompt Shows a message with OK and Cancel buttons, and prompts the user to input a value. Both callbacks receive the entered value as the first parameter. Type: prompt Available button callbacks: ok, cancel Verify Verify shows a message with Yes and No buttons. Type: verify Available button callbacks: yes, no Events No events are emitted by the alert widget; since callbacks are supported, custom events can be triggered inside them to achieve desired functionality.
  12. Initializing widgets UI widgets can now be created on elements without needing to write any code. This is done by specifying special attributes on the element using the HTML5 data attributes. Widgets each define their own behaviors - some wait for user interactions, others might immediately change the display when executed. Usage Each UI widget has a base attribute that identifies it, and causes the element to be passed to the widget module to be handled. All widget attributes have the prefix ips. <!-- If a widget called 'Widget' existed, we could initialize it on an element like this --> <div data-ipsWidget> ... </div> In this example, ipsWidget would be referred to as the widget key. The data- part of the HTML attribute is implied throughout the documentation for individual widgets. Each element can have multiple widgets specified; simply add each necessary widget prefix to the element as above. That said, bear in mind multiple widgets on a single element might cause conflicts if more than one responds to a user interaction. Test to be sure. Note: In many cases, the helpers available in the IPS4 PHP framework will generate all of the required HTML for you, including widget attributes, meaning you won't need to manually specify UI widgets in your HTML. For example, the autocomplete form input type adds the necessary attributes, so you don't need to handle it yourself. Consult the PHP documentation for more information. Passing options Most widgets have a number of options that control their behavior. To pass an option, use this format: <div data-ipsWidget data-ipsWidget-option1='Option value' data-ipsWidget-option2='Another option value' > ... </div> Each option is added as an additional attribute on the element, with the attribute key being data-ipsWidget- followed by the option name. By default, options are passed as strings because they come from the DOM. However, if the option contains only numbers, it will be cast explicitly as a number when passed into the widget. If the option contains the strings true or false, it will be cast as a boolean. In addition, options without a provided value are treated as the boolean true, meaning it is possible to specify true values like so: <div data-ipsWidget data-ipsWidget-showWide> Automatic initialization Widgets are automatically initialized when the data API is used as described. Further to that, when new content is loaded into the page (such as when an AJAX request fires), any widgets within the new content will also be automatically initialized.
  13. What's a widget? UI widgets are modules that: Are instantiated on individual DOM nodes Provide some kind of UI functionality Benefit from being reusable Come with an automatic data API functionality A UI widget is created as a standard module, under the ips.ui namespace. Basic widget boilerplate ;( function($, _, undefined){ "use strict"; ips.createModule('ips.ui.widgetName', function(){ var respond = function (elem, options, e) { }; // Register this module as a widget to enable the data API ips.ui.registerWidget( 'widgetName', ips.ui.widgetName ); return { respond: respond }; }); }(jQuery, _)); In the example above, a module is created with the module path ips.ui.widgetName. UI widget modules must expose a response method, either the default respond method or by manually specifying a method when the widget is registered (see below). This is the method that is called when the widget is instantiated. Registering a UI widget A module is registered as a widget by calling the ips.ui.registerWidget method before the module returns its public methods object. ips.ui.registerWidget( string widgetKey, object handlerModule [, array acceptedOptions] [, object widgetOptions] [, function callback] ); The method takes the following parameters: widgetKey (required) The key that is used to reference this widget throughout the software. The key is prefixed with ips when used, to prevent naming collisions. The key forms both the data API attribute key, and the name of the jQuery plugin that can be used to instantiate the widget. Assuming the widget key is set to widget, the data API attribute key and jQuery plugin would be: data-ipsWidget // Data API attribute $('element').ipsWidget(); // jQuery plugin method handlerModule (required) A reference to the module that will respond when the widget is instantiated. In practice, this should be a self reference to the module that is being defined. acceptedOptions (optional) An array of strings defining the options that this widget will accept. When the widget is instantiated, if these options are defined their values will be passed back to the respond method. widgetOptions (optional) An object containing options for how this widget will be registered or instantiated. The available options are: lazyLoad By default, widgets are instantiated as soon as they are seen in the DOM. By setting this option to true the widget is not instantiated until the user interacts with the element. lazyEvent If the lazyLoad option is enabled, this option defines the event that will be watched for. When the event name set here is triggered, the widget will be initialized. makejQueryPlugin By default, widgets will have an associated jQuery plugin created to allow them to be instantiated programatically. This functionality can be disabled by setting this option to false. callback (optional) A widget must have a response method, which is called when it is initialized on an element. By default, a method with the name respond is looked for, but a different response function can be set by passing it as this parameter. Events The javascript framework in IPS4 relies heavily on events. Controllers in particular are completely decoupled and cannot directly communicate with one another. Instead, communication happens by triggering and receiving events. It is therefore important that your widget emits events when key actions happen. This allows controllers, other UI widgets, even third-party scripts to respond when something happens within your widget. As an example, the ips.ui.menu widget emits a number of events, including menuOpened, menuClosed and menuItemSelected. Each of these events also provides a number of data items relevant to the event, which can be used by event handlers listening for those events. It is this event interaction that facilitates dynamic pages within the IPS Community Suite, so it's important your widget also emits relevant events. When you emit an event, it is usually most appropriate to emit it on the element the widget is registered on. Emitting an event uses the standard jQuery syntax: $( elem ).trigger( 'myWidgetEvent', { color: 'red', size: 'large' } ); Widget initialization A widget is initialized either as soon as it is seen in the DOM, or, if the lazyLoad option is enabled, when the user interacts with the element the widget is created on. In both cases, the initialization process is the same. The internal widget manager will call the response method for the widget (respond by default, or whatever function is passed as the callback option), passing in the following parameters: function respond( element elem, array options, event ev ) elem A reference to the element on which the widget has been initialized options An array of options that have been specified on the element, and which were in the list of expected options when the widget was first registered. Any other options are ignored and won't be passed through. ev If the widget is initialized in response to a user interaction because lazyLoad is enabled, this parameter will contain the original event object. It will be undefined if the widget is being initialized on load. Example widget Let's take a trivial example, and assume we're creating a widget which hides an element: ;( function($, _, undefined){ "use strict"; ips.createModule('ips.ui.hideElem', function(){ var respond = function (elem, options, e) { if( options.animate ){ $( elem ).fadeOut(); } else { $( elem ).hide(); } $( elem ).trigger( 'hiddenElement' ); }; ips.ui.registerWidget( 'hideElem', ips.ui.hideElem, [ 'animate' ], { lazyLoad: true, lazyEvent: 'click' } ); return { respond: respond }; }); }(jQuery, _)); When we register the widget, we set up an animate option in the acceptedOptions parameter. In the widgetOptions parameter, we set lazyLoad to true, and the lazyEvent to click - meaning our widget won't be initialized until the user clicks on the element. In our respond method, we simply check if options.animate is true, fading out if so and hiding if not. This example widget could be initialized on an element like so: <button data-ipsHideElem data-ipsHideElem-animate='true'>Click to hide me</button>
  14. Overview Controllers are special modules that handle specific functionality on specific pages. They are not necessarily reusable in different contexts, although some may be. A controller is initialized on an individual element, and that element becomes the controller's scope. A controller responds to user events (such as click) and events triggered by UI widgets or sub-controllers, and manipulates its scope element accordingly. The scope of a controllers functionality is entirely flexible. A controller might be initialized on a very small fragment of the page and perform just one task, or it might be initialized on a main wrapper and handle functionality that applies to the whole page. A controller can be initialized on an element even if it's a descendent of another element with a controller; this is a common pattern whereby the child controller can emit events that the parent controller can respond to. Generally, keep a controller narrowly focused on one aspect of the page. If a controller ends up handing distinct and unrelated functionality, split it into smaller controllers. Creating a controller A controller is registered in a slightly different way to standard modules; instead, an object literal is passed to the method ips.controller.register: ;( function($, _, undefined){ "use strict"; ips.controller.register('type.controllerID', { initialize: function () { // Controller events are initialized here } }); }(jQuery, _)); A controller, at the very least, must define an initialize method, which is called when the controller is initialized on an element. The initialize method should set up events that are handled by this controller only; if other initialization tasks are necessary, it is recommended that you define a setup method within the controller, and call that at the start or end of the initialize method as appropriate. Within the controller definition, this refers to the controller instance. That means controller methods can be called internally with this.someMethod(). A reference to the element that the controller is initialized on (its scope) is available with the scope property: // An example which hides the element this.scope.hide(); Method and property names For clarity, it is recommended that event handler method names (i.e. the handlers to events you set up in initialize) are not prefixed with an underscore, while other methods and properties of the controller are. This helps create a clear distinction between core functionality of the controller (the event handlers) and supporting methods. Handling events Event handling is the core purpose of a controller. There are three special methods available for watching and triggering events. Two are named after their jQuery counterparts, but add controller-specific behavior. this.on Used for watching and responding to events. this.on( [element elem,] string eventType [, selector delegate], function callback ); elem A reference to the element on which the event will be watched for. If not specified, this.scope is used by default. eventType The event being watched for. This can be a default browser event, like click, or events from widgets and models, like menuItemSelected. delegate A delegate selector can optionally be specified. See the jQuery documentation for more information. callback A callback function, called when this event is observed. The function specified here is automatically bound to this, so internal methods can be passed simply by referencing them like so: this.on( 'click', this.internalMethod ); this.trigger Used to emit events that originate from this controller (specifically, the scope element). this.trigger( [element elem,] string eventType [, object data] ); elem A reference to the element on which the event will be triggered. If not specified, this.scope is used by default. eventType The event being triggered. data An object containing data relevant to the event. this.triggerOn Used to emit events directly on sub-controllers from a parent controller. this.triggerOn( string controllerName, string eventType [, object data] ); controllerName The controller name to find and trigger the event on. The wildcard character (*) is supported at the end of this parameter and the event will be triggered on all matching controllers. Examples: core.front.core.comment core.front.core.* core.* eventType The event being triggered. data An object containing data relevant to the event.
  15. Note: This documentation assumes you are running your installation with IN_DEV enabled. Due to the multi-application model that the IPS Social Suite employs, and the way that resources are bundled and served to the browser, there are a number of places in which different types of javascript files are stored. Development & production When developing for IPS4, you create your javascript files in the application or global directories, with a single file holding a single module. In production, however, IPS4 bundles related files together (concatenating and minifying them in the process) to reduce file size and the number of HTTP requests needed to fetch the scripts. You should be aware of how the bundles are built and from where, because this will dictate how you specify javascript files to load in your backend PHP code. Global resources Global resources are those that are loaded on every page load. This includes: Libraries - jQuery, Underscore etc. Application setup - Bootstrap module, loader module, base controller etc. Global modules - UI widgets, utilities, global controllers etc. Front/admin modules - Controllers global to the front-end/admin control panel, needed on every page in that area. All of the above files are located at <root>/dev/js/. Application resources Applications have their own folder for javascript, located at <appName>/dev/js. Within this folder, there should be three subfolders - admin, front and global. These folders hold javascript specific to those areas (or for the whole application, in the case of global). Within each of those folders respectively, there are further subfolders for different types of javascript file - controllers, templates and mixins. Therefore, within an application, the file structure looks like this: /appName /dev /js /front /controllers /profile ips.profile.main.js ips.profile.body.js /templates ips.templates.profile.js /mixins /admin /controllers /templates /mixins /global /controllers /templates /mixins Bundles for an application are built based on folder/filenames, and use the format <location>_<directory>.js. Taking the structure above as our example, if we loaded front_profile.js, it would contain: /js/front/controllers/profile/ips.profile.body.js /js/front/controllers/profile/ips.profile.main.js /js/front/templates/ips.templates.profile.js This bundle would then be included in your PHP code like so: \IPS\Output::i()->jsFiles = array_merge( \IPS\Output::i()->jsFiles, \IPS\Output::i()->js( 'front_profile.js', 'core' ) ); Third-party libraries An application has an interface directory at <appName>/interface/. This is where third-party libaries used by individual applications should be stored and loaded from. They are not bundled or minified by IPS4, so you should store the pre-minified rather than the uncompressed version. Assuming you had added a file to interface at <appName>/interface/jquery/jquery.rangyinputs.js, it can be included in your PHP code like so: \IPS\Output::i()->jsFiles = array_merge( \IPS\Output::i()->jsFiles, \IPS\Output::i()->js( 'jquery/jquery.rangyinputs.js', 'appName', 'interface' ) );
  16. Global scope & closure wrapping Variables must not be leaked into the global scope unless there is an explicit functional need for global access. Within function scope, variables must be declared with the var statement. To help prevent scope leaking, each script file should be entirely wrapped in an anonymous, self-executing closure, passing the jQuery and Underscore objects as parameters: ;( function($, _, undefined){ // Code goes here }(jQuery, _)); This prevents variables from leaking into the global scope, and also ensures jQuery code making use of the dollar variable $ does not conflict with any other code that might be running on the page. Note that a semi-colon appears as the first character. This helps prevent errors caused by incorrect semi-colon insertion in previous code, should this script be concatenated with others. It is a defensive coding practice, and recommended in all files. Strict mode The use of strict mode is required for all default javascript in IPS4. Strict mode helps prevent leaking into the global scope, catches code problems that would otherwise fail silently, and enables javascript engines to perform more optimizations. However, it is important that this is applied using the per-function syntax and not the per-script syntax, otherwise unexpected behavior can occur when script files are concatenated with 3rd-party scripts. Given that all script files should be wrapped in a self-executing closure, the syntax for enabling strict mode looks like this: ;( function($, _, undefined){ "use strict"; // Script here, e.g. module definition etc. }(jQuery, _)); By putting the "use strict"; statement inside the the closure, we ensure all of our code runs in strict mode without affecting any external code by accident. Note that the strict mode statement must be the very first line of the function, as shown. Further information about strict mode and the code requirements it imposes is available at MDN. Documentation & commenting The use of JSDoc is highly encouraged to document code. Refer to the JSDoc documentation for exact syntax. Within scripts, comment blocks /* */ should be reserved for formal documentation and situations where large blocks of existing code need to be commented out temporarily. Line-by-line comments should always use the single-line // comment style. /** * Formal documentation */ // Informal comments to describe code Semi-colons Semi-colons must be used at the end of every statement, even when function/object literals are being assigned. For example: // Simple statements $('#element').doSomething(); var test = 'value'; return 'text'; // Function and object literals var test = function () { ... }; var test2 = { key1: 'value' }; Names All names (variables, methods, properties etc.) should use lowerCamelCase, such as deselectAll, createModule and itemSelector. Logging You should not use console.log for debugging. This does not provide for disabling debugging in production, and can break pages on older browsers if not accounted for. A debugging method is provided via Debug.log that addresses these concerns, and should be used instead. Line length Aim to keep the line length below 120 characters to maintain readability. Longer lines should be split immediately after a comma or operator, with the remainder of the statement indented further to ensure clarity. For example: if( ( options.dataSource && options.dataSource.indexOf('#') === 0 && $( options.dataSource ).length ) || originalTextField.is('[list]') ){ jQuery chaining It is recommended that multiple jQuery methods chained on an element are placed on newlines to improve clarity. Additionally, indentation should be used to reflect the current working level of the chain. For example: $('selector') .find('.classname') .hide() .end() .find('.otherClassname') .css('width', '100%') .show() .end() .append( $('<div/>').attr( { id: 'someID' }); By indenting in this way, we make it clear that the hide() method applies only to the elements matching .classname, before calling end() to return to the original working level - and so on.
  17. In IPS4, most javascript is split into self-contained, named modules. Modules and javascript files have a one-to-one relationship - each javascript file should contain one module only. Modules prevent variables leaking into the global scope (which must be avoided), and helps ensure code stays focused. All modules appear under the ips namespace. Types of Module A number of primary types of module exist within the software: Utility Modules Exist on the ips.utils namespace, and provide assorted functionality that other modules can make use of, including cookie handling and methods for handling responsive design. UI widget modules Exist on the ips.ui namespace, and define interface widgets to add interactivity to pages, and which can be reused using special data attributes. Controllers Controllers are modules that handle specific functionality for one particular page or part of a page. They respond to events from UI widgets and other controllers and update their scope accordingly. Mixins Mixins are used to extend controllers with additional functionality. Other types There are a number of other types of non-modularized file used too: Libraries/interfaces Libraries are 3rd party scripts that provide additional functionality. jQuery and Underscore are two examples. Templates Template files define special Mustache templates, that modules can use when rendering HTML to the browser. Languages Language files define strings which can be translated into other languages.
  18. I want to briefly show our new cover photo support. Cover photos allow users to upload an image to represent something in the community; we currently support them in profiles and calendar events and may roll out support to other areas later. Here's a video of it in action for a calendar event. It's really simple to use, and of course still works responsively like the rest of our default theme. We hope it adds a new element of customization for content in your community. Developers For developers, supporting cover photos in your own addons is as easy as you'd expect. A helper is available which handles the nitty-gritty for you; you simply add $item->coverPhoto() to your template, override a couple of methods in your controller, and optionally build your own menu to control the user interaction (or you can let the helper output them for you, as in the video above). That's it! As always, screenshots are from pre-release software and are subject to change before release.
  19. We've previously shown how responsiveness works in the AdminCP, but I'd like to briefly introduce responsiveness on the front end, and pick a few views to show you as examples (this will be a screenshot-heavy entry!) What is responsiveness? Before we get to that, allow me to recap what responsiveness is. Responsive design is a method by which you design one page in such a way that it adapts for the available screen space on the device the user is using. This means that one theme handles both the full desktop view and the condensed mobile view with some clever CSS, in contrast to 3.x where we had a separate mobile skin. When we took the decision to use responsive design for IPS4, one key aim was to ensure that the mobile view isn't feature reduced. We want all functionality and all areas of the suite to be available regardless of device, and with only a couple of exceptions we're on track to deliver this. Primary navigation In mobile view, the primary navigation collapses and moves to a menu accessible with the icon in the top-right. The breadcrumb becomes a 'Back' control, taking you up a level from the current page: The primary navigation, when opened, looks like this: Moderation Given that the responsive theme supports all functionality, this naturally includes moderation. IPS4 support full moderation capabilities regardless of the device you're using. Here's an example of moderating images in Gallery. Notice the menu to quickly select types of content to moderate, as well as the floating toolbar at the bottom of the screen to choose actions. Settings page Taking the settings area as an example, here's the same screen at the three supported breakpoints - desktop, tablet and mobile. Profile view Here's profile view (which we covered in more detail here) as seen on a phone: Calendar Calendar views on mobile: Gallery Viewing albums & images in a category: Blog The blog homepage: And viewing a blog: Forums Submitting a topic on mobile: Conclusion So that wraps up this round-up of responsive views. Naturally, there's many more views than this in the suite and we can't show screenshots of every single one, but hopefully this entry has given you a taste of a variety of views, and a better idea of how we're approaching mobile users in IPS4. As always, screenshots are from pre-release software and are subject to change before release.
  20. Profiles are one of the key sections of a community, as everyone knows. They are what represent your users; where their information is shown and their content is gathered. When users contribute quality content to your community, their profile is where other users go to find it in one place. In short, it's an important area. In IPS4, profiles have had a complete makeover. There's a lot to cover, so I'll start with a numbered screenshot, and address each section individually (please note this is a large image; if you're on mobile, you may wish to wait to view it full-size). 1 - Header images In 3.x, users could customize their profiles by uploading a background image. In practice, this didn't work well when the software was integrated into an existing website design, and the options presented often ended up with a garish profile. In addition, social networks like Facebook and Twitter have adjusted user expectations on how profiles are customized. In IPS4, instead of page backgrounds, users instead get to customize their profile header image. This provides the best of both worlds - ample space to choose something creative, but it's contained and won't mess up a website design. 2 - Reputation The user's current reputation count is shown prominently in the info column, letting other users know if this member is an asset to the community. 3 - Warnings For moderators/staff, the profile now provides quick access to warning tools. By expanding the panel, they can see a brief history of recent warnings: And clicking one of these pops up the warning details: New warnings can also be issued inline, of course. 4 - Followers Followers replace friends in IPS4, and the user's followers are shown in this block. Instead of requiring mutual acknowledgement as with the traditional friends system (an approach that isn't entirely useful in a community of anonymous users), in IPS4 you follow users whom you find interesting in order to be updated when they contribute to the community. Users can of course prevent others from following them, if that is a concern to them. We'll have more details on how followers works in a later entry. 5 - About the user Traditional information about the user is shown in the next block, including custom profile fields. 6 - Recent visitors Recent visitors to this user's profile are shown next. As with 3.x, this can be toggled on and off by the profile owner. In 4.x, this is done by clicking the X in the corner of the block. 7 - Follow/Message member These primary buttons enable others to follow the user (if enabled), and send a new message inline, without leaving the page. 8 - User's content In 3.x, browsing a user's content was handled by the search area of the community (though links were available in the user's profile and hovercard). We felt this wasn't the best place for it, though. After all, a user's content should be available in their profile. That's what this button does. It switches the profile view to 'content browsing' mode, where you can see everything the user has done. It's smooth and buttery, and because it all loads dynamically, it feels like a true part of the profile. Here's a video of it in action (14MB) 9 - Long-form custom profile fields IPS4 supports various kinds of custom profile fields, including rich-text editors for long, styled content. Those custom profile fields will be shown in the main section of the profile where they get the space they need to be effective. About Me is a default field, but you can of course add your own too for your users to fill in. 10 - User's 'Nodes' A node is a fancy developer term for content containers that a user creates themselves, like gallery albums and blogs (as opposed to forum categories, which are created by the admin). In IPS4, a user's 'nodes' are shown right on their profile page, making it easy to find more interesting content from the user. In this screenshot, you can see my profile is showing my albums, my blogs, and other blogs to which I contribute. For developers, supporting your application in this section is easy too. 11 - Status feed The status feed from 3.x is of course still present, and the interaction is all inline without leaving the page. Conclusion That's profiles in 4.0. We hope the new focus on content and streamlined design provides a better experience for your users! As always, screenshots are from pre-release software and are subject to change before release.
  21. IP.Board 3.x supports "My Media", which enables you to share other content from within the community by using the "My Media" button on the editor. This results in: http://community.invisionpower.com/files/file/4464-ips-gdk-for-ipboard-32-amp-33/ While this works, it has a few shortcomings: The styling of the block isn't really designed for each type of content it might show Users have to click the My Media button, then browse for the item, when they probably already know the URL they want to link to Not all content types are supported; e.g. you can't use My Media to link to a topic. For developers, implementing support for My Media in other applications was a process involving extension files and multiple methods We wanted to make sharing existing content much easier in IPS4, both for users and developers. "Embeddable content" is our solution. How to use it To embed content from elsewhere in the community, here's a step by step guide: Paste a link to it That's it! When you paste a link to almost any kind of content, whether it's a forum topic, calendar event, gallery album or more, IPS4 will automatically embed a small preview of the content, designed specifically for that content. In order to not disrupt an existing paragraph of text however, the embedded block won't be used if the link is surrounded by text. Embedded content only shows if the link is pasted on its own line, giving users more control over their post. Here's what a post looks like with a few embedded types shown: Embedded content can be used anywhere as you'd expect, including posts and comments, but also status updates, IP.Content articles, and so on. For developers Supporting embedded content in your apps is very easy; your content model simply has to implement IPSContentEmbeddable: class _Topic extends IPSContentItem implements ... IPSContentEmbeddable Your controller then simply looks for an embed request and returns HTML - that's it. Our default blocks also have their own template and CSS file, so theme designers can change the styling on a per-theme basis. Conclusion Our hope is that this easier method of embedding content encourages more cross-posting and highlighting of good content in IPS4. The process is almost wholly automatic, meaning users don't have to think in order to share great content with others. As always, screenshots are from pre-release software and are subject to change before release.
  22. One of the most distinctive uses for a forum is that of a 'knowledge community', where users visit in order to get help with a problem or question. Our own Pre-sales forum uses this model, but we also have many customers who run forums that are almost exclusively knowledge-based (such as Roxio and Evernote). IP.Board 3.x introduced the concept of a "Best Answer" flag, allowing topic creators and staff the ability to highlight the reply to a topic that they deem best answers the question. This shows a snippet of the post in green at the top of the topic. Many sites now use this feature, but for IPS4 we wanted to expand the functionality offered for these types of forums. Question & Answer Forums Forums in IPS4 will enable you to set a forum as a "Q&A Forum". This adjusts the forum to be specifically designed for knowledge sharing. Instead of topics and posts, it has questions and answers. On the forum index, the forum will be shown as a Q&A forum with its forum icon (unless you've set a custom forum icon for that forum): Forum View When you enter the forum, instead of the normal topic listing, you see a list of questions: You'll see here that questions that have a best answer are indicated with a green checkbox. You'll also notice that one of the stats on the right hand side is 'votes'. In Q&A forums, questions can be voted up or down by users, in order to give them more visibility. More popular questions will bubble to the top (depending on the age of the question). You can of course still order by more traditional methods, if you wish. Popular questions from the past 30 days are also highlighted at the top of the forum, providing an up-to-date 'knowledgebase' that other users can see. Using our own presales forum as an example, if someone asked a question about an important feature and it was voted highly, other users visiting the forum would see it right at the top, which is great for content visibility and helping users get the answers they're looking for with minimal fuss. Question View Clicking into a question shows an adjusted topic view: The question (i.e. the first post) is shown at the top of the page on all pages, with answers listed below. You'll see that replies can also be voted up and down - in fact, this determines the order in which answers are shown inside the question. Popular answers, as determined by the community, will appear at the top, with worse or incorrect answers being pushed down. This is great for quickly finding the best information for the question at hand; in IP.Board 3.x, all too often a high-quality answer will appear in the middle of a topic and unfortunately go unnoticed by the topic creator or others looking for an answer. You can still sort answers by date, if you prefer. In the screenshot above you can also see the first post is marked as the best answer. "Best Answer" always appears at the top, regardless of its vote count. Question/answer ratings are separate from reputation, so you can of course still "Like" posts even if you don't think they're a good answer to the question. Conclusion So that's the new Q&A feature for IP.Board. We think it'll a big step forward for knowledge-driven communities using IP.Board, or even individual forums in other communities (like our pre-sales forum), helping users find answers to their questions more efficiently, and ultimately making your communities more useful. As always, screenshots are from pre-release software and are subject to change before release.
  23. Different staff members typically have different roles within a community - especially larger communities, where you may have staff responsible for the theme, others handling tickets and different staff maintaining the system. In 3.x, we had a 'Bookmarks' system in the AdminCP that allowed you to create a menu of frequently-used sections in an effort to make them easier to get to, rather than navigating the main menus. As with every feature in IPS4, we took some time to think about what this Bookmark feature aimed to achieve, and whether it was the best way to achieve it (seriously - we have considered everything you'll see in IPS4 very carefully; nothing gets a free pass). We determined through speaking to administrators that the primary use of this feature was actually to get to one place quickly - whatever place that might be. It appeared to be rarely used as an actual bookmarks menu, and besides, duplicating browser functionality should always send up a red flag. We decided to rethink the idea. What we decided to do instead is allow AdminCP menus, both primary and secondary, to be reordered on a per-admin basis. This means each admin can set the AdminCP menu up however works best for themselves. If you use the theme system a lot, you can make that your first menu item. Or, if you use the ticket system in Nexus, you can put that first. Here's how it works:
  24. The submissions process in IP.Downloads has a certain complexity that may not be apparent at first. As well as simple file uploads, we also support adding files from URLs and from the local file system, and screenshots can also be added in these ways. Which category you choose to submit to affects which of these options are available. In addition, via the AdminCP you can bulk-upload files - but not via the front-end. For IP.Downloads 4, we wanted to improve this process with interface and functionality changes. Submitting Files Here's a video demonstration of how creating a single file record in IP.Downloads works in v4: We've worked hard to improve the flow for users here - while they are uploading files (which may be large and take some time), they can continue adding the other file information such as screenshots and meta data. While that's happening, the upload progress is always shown at the top of the screen. In the video you'll also see how image uploading is handled, as well as prefixes in the tag system, which are now supported throughout the IPS Community Suite. Bulk Uploading Instead of going to the AdminCP to bulk-submit files, single- and bulk-uploads are now handled through exactly the same process on the front end. This means users can be granted bulk-upload permissions without requiring AdminCP access, a big improvement on the current implementation. To bulk upload, a user clicks the "Submit a file" button as normal, and chooses "I want to submit multiple files at once". They see the same upload area, but this time, the file information step is handled separately after the page is submitted. Each uploaded file has a separate block for file information and its own set of screenshots. We'll of course be showing more of the IP.Downloads homepage and file view later, but we hope that gives you a taste of what to expect in IP.Downloads in IPS4.
  25. Reminder: this blog covers the technical details of 4.0's programming. For details on 4.0's features, follow our main blog. Reviewing controllers Some time ago, I blogged about the javascript framework we've built for IPS4. In it, I covered the most important component: controllers. To recap, a controller is a special object within the framework, and is applied on specific elements. That element is the controller's scope, and the controller works on it to provide its functionality. For example, a simple controller might look like this: ips.controller.register('core.global.core.example', { initialize: function () { this.on( 'click', this.showAlert ); }, showAlert: function (e) { alert( "This button's text is: " + this.scope.html() ); } }); It would be used on an element like so: <button data-controller='core.global.core.example'>Click me</button> Here we're registering core.global.core.example as a controller. This represents the controller path - it's in the format app.module.group.controllerName. Though it seems longwinded, this allows IPS4 to dynamically load controllers on-demand, rather than loading them all when the page is loaded. In the initialize method (called automatically), we set up an event handler for a click. When the element is clicked, you'd see an alert saying "This button's text is: Click me". So, that's how controllers work. Almost all page behavior in IPS4 is handled through controllers. But how would you change a method in an existing controller, say if you were writing an addon, or if you had two controllers that were fairly similar, and wanted to provide a base controller they both shared? Mixins To enable that, we have mixins. Mixins allow you to specify functions which are inherited by objects - in this case, our controller objects. This means, using mixins we can add new functions to a controller without needing to edit the controller itself. A mixin is defined like so: ips.controller.mixin('addAnotherMethod', 'core.global.core.example', true, function () { this.anotherMethod = function () { alert('Inside anotherMethod'); }; }); The ips.controller.mixin method takes up to 4 parameters: ips.controller.mixin( name, controller, automaticallyExtend, fnDefinition ) name: Name to identify this mixin controller: The controller this mixin extends automaticallyExtend: [optional, default false] Does this mixin automatically extend the controller (more on that below) fnDetinition: The function definition applied to the controller In this example, our mixin adds a method named anotherMethod to our core.global.core.example controller shown earlier. Let's talk more about the automaticallyExtend parameter. Mixins can be applied to controllers in one of two ways - either automatically on a global basis, or manually on a case-by-case basis. Mixins are manually specified like so: <button data-controller='core.global.core.example( addAnotherMethod )'>Click me</button> This means the mixin is used on this element - but another element using core.global.core.example wouldn't get it. This is useful when you're building your own apps or addons; you can write simple controllers that implement base functionality, then extend them with functionality for specific cases by specifying the mixin name in your HTML. We use this ourselves - for example, we have a base table controller that handles sorting, filtering and so forth. We then have a mixin for AdminCP tables, and a mixin for front-end tables, which add functionality specific to those areas, reducing code duplication. If you're extending an IPS controller in an IPS app, though, modifying the HTML isn't typically an option. Instead, you can specify the mixin as global, and it will be applied to all elements where that controller is used. This means you can write your own mixins that work with our default controllers without having to touch our controller code (and that's a good thing). Advice So that shows how to add new methods to a controller. But what if you want to work with the methods that already exist in the controller? In the above example you'd only be able to overwrite an existing method - certainly not ideal, because 1) you would break any other mixins using the default method, 2) if we made an update to a method in a later release, your mixin would break it. To facilitate working with existing controller methods, we're using a model called advice. This adds three special methods to a mixin: before, after and around. These let you 'hook into' existing methods and provide additional code for them. Let's rewrite our example mixin from above to take advantage of it: ips.controller.mixin('changeBackground', 'core.global.core.example', function () { this.before('showAlert', function () { this.scope.css({ background: 'red' }); }; }); Here, I'm using the before method. I'm hooking into showAlert method (from the controller), and changing the background color of the scope element. So what happens when the link is clicked? First the background changes to red, and then an alert box is shown. We've added the background changing functionality without needing to edit the controller at all. Here's two other ways of doing the same thing, using the other two special methods: ips.controller.mixin('changeBackground', 'core.global.core.example', function () { this.after('showAlert', function () { this.scope.css({ background: 'red' }); }; this.around('showAlert', function (origFn) { this.scope.css({ background: 'red' }); origFn(); }; }); The after method is fairly self-evident. With the around method, the original function is passed in as an argument, allowing you to determine when it is executed by your mixin. All three of these methods will stack, so multiple mixins can hook into the same method, and they'll be executed in order, each receiving the previous. Conclusion I hope this introduction to mixins proves useful to developers; it shows how our core app controllers can be extended in a non-destructive way, but also how your own apps can use the mixin functionality to create an inheritance model to make your life easier. Javascript in IPS4 makes extensive use of custom events, so the preferred way of adding new functionality is to listen for appropriate events and act on them - but the mixin support described above provides a mechanism by which you can adapt existing event handlers.
  • Create New...