Portfolio

Purpose
Portfolio was made to act as a bridge to accept a recognizable option table format (similar to Khaos and Ace) and convert it into a fully functional Blizzard Options Panel. Any Ace or Khaos registration should be fairly painless for an author to convert to use Portfolio, and new option sets should be easy to understand and construct without having to make any frames or manually manage controls.

Feedback & Support
The best way you can help would be to try and write your own Portfolio config options and see if it behaves the way you'd expect. Report any bugs or confusing implementation using the Report Bugs button or Comments. I'm also always open to suggestions and code donations via the Feature Request link.
If you'd like to donate to show your support, that can be done through paypal with a paypal account or by credit card. Remember donations are much appreciated but non-contractual. Thank you!

Implemented Option Types

Header - CONTROLTYPE_HEADER

Text - CONTROLTYPE_TEXT

Checkbox - CONTROLTYPE_CHECKBOX

Slider - CONTROLTYPE_SLIDER

Button - CONTROLTYPE_BUTTON

DropDown - CONTROLTYPE_DROPDOWN

ColorPicker - CONTROLTYPE_COLORPICKER

EditBox - CONTROLTYPE_EDITBOX

Window - CONTROLTYPE_WINDOW

Option Attributes

Common Option Table Attribute Descriptions

id - Unique option id. Used to create the names of the option frames (No Spaces!). Key used to store the value in the saved variables table if cvar/uvar/tvar are nil.

Notes on Dropdowns
This implementation of dropdowns is about half way between managed and unmanaged. It has some shortcuts, but is essentially blizzard's dropdown code at its core. Because of this it is very powerful and can be customized by advanced users to do almost anything. But for those of you new to dropdowns you don't have to be intimidated, because I've done all the setup for you. The minimum you have to supply is a table formatted list of buttons. Each button should have a text, and a value. The checks and callback will be managed if left empty.

For advanced users the func and checked attributes can be overriden. You can also use any of the normal dropdown attributes. See FrameXML/UIDropDownMenu.lua for a full list of menu item attributes. And as will all controls you can override any of the settings or methods using the init function if you need something more customized.

I've also simplified the dropdown button itself, using UIDropDownMenuTemplate as a template on top of which I've added shortcuts and modifications. Texture anchors have been modified to play nicely with control:SetWidth(x) so that you don't have to use UIDropDownMenu_SetWidth. The clickable/tooltip area has also been extended to cover the whole button.

Saved Variables
Portfolio uses LibDefaults to manage your defaults for you if you wish. The normal way to handle defaults is to put the name of your saved variable table in optionSetTable.savedVarTable, but you can also supply individual options with their own varTable. Then you can supply a tvar to be the index to that saved variable table, otherwise the id will be used as the index. Alternately you can supply an option with a cvar or uvar (global variable name) to use instead. Note that if you supply none of the above Portfolio simply won't handle your variables. You can then manually update the options using the callbacks and init functions. You can also supply optionSetTable.initCallbacks = false in order to disable callbacks being fired when after the variables load.

Since Portfolio has no saved variables of its own and allows you to be flexible with your saved variable structure you can easily use the same saved variables with or without Portfolio. Your Portfolio GUI options can be truly optional. I'd suggest using LibDefaults to save live memory and avoid saving/loading in default values, but you don't have to.

Registration

lua Code:

-- Get a reference to the lib from LibStub

-- If LibStub doesn't exist this will fail silently

-- If LibStub exists but Portfolio does not this will print an error in chat and then fail

-- Use LibStub("Portfolio", true) to fail silently if you want Portfolio options to be optional.

Examples
The PortfolioDemo addon is included in this package. It is disabled by default so it won't bother users, but it should be an invaluable resource to those of you who learn by example. It should have a variety of usages for each implemented option type.

v1.23
- Added static 'width' option for Dropwdown controls

v1.22
-Fixed a bug where disabling the about panel also disabled initial loading callbacks

v1.1
- Added CONTROLTYPE_WINDOW for child frame control windows (optionally scrollable)
- Modified Scrollbars to be inside the window, have a border, and still be scrollable while moused over
Window width is now modified depending on the visibility of the scrollbar.
- Renamed UpdateTextWrap to UpdateBox and it is now called on child controls when the window scroll box is changed
- Fixed an error with dropdown entry selection
- Modified SetRelativePoint for better extensibility:
Added xOffset and yOffset fields for modifying the default offset from the anchor control.
Added xOffsetRelative and yOffsetRelative fields for modifying the default offset of relatively anchored controls.
xOffset and xOffsetRelative are also used in calculating the default width for windows.

v1.0
- Added CONTROLTYPE_EDITBOX
- Removed isTemp and replaced it with isGUI which is only a passthrough value to the callback; text and saved var are still updated.
It is now: callback(value, isGUI, isUpdate)
isGUI is now passed as true for all GUI control interactions (but not for the Okay, Cancel and Default blizzard option panel buttons).
- Added control:Refresh() called when the blizzard options frame is shown
- Added control:Okay(), control:Cancel() and control:Reset() that are called for each control that has them when you click the blizzard interface buttons
- Fixed Cancel to correctly revert values/controls to their previous state. Doesn't pass isGUI.

v0.9
- Added CONTROLTYPE_TEXT
- TODO: Fix Text height/wrapping
- Added control:Reset() - Disable() no longer resets.
- dependentOptions will reset after disabling, but dependentControls will not
- UpdateDependentControls renamed UpdateDependents, handles both dependentOptions and dependentControls
- slider:SetMinMaxValues(min, max) now updates the stored min/max and texts if minText/maxText are not set
- Enable/Disable now changes text color
- tooltipText is now as flexible as text
- Headers and Text controls now dynamically resize with text:UpdateTextWrap() called OnShow
- Refactored and abstracted much of the control functionality to Portfolio.Control and renamed files.
- Fixed a bunch of minor bugs

v0.8
- Text can now have %s or %d to be formatted with the option value. control:UpdateText() is called when an option is updated
- Added dependentControls and dependentOptions for CONTROLTYPE_CHECKBOX to Enable/Disable other options
- control:Disable() now resets the value to default
- Cleaned up OnShow code so it doesn't call callbacks excessively
- callback(value, isUpdate) now pass an extra boolean argument when called by control:Update() (used when loading vars)

v0.7
- Working Slider option

v0.6
- Now uses LibStub to handle library versioning
- Removed event handling code and default variable initialization in favor of using LibDefaults
- Upgraded to InterfaceOptionAboutPanel lib
- Removed noAutoDefault. Now always loads vars that have defaults set.
- loadVars is renamed initCallbacks and now defaults to true
- Changed how GetValue/SetValue/Update works on the options

v0.3
- Only loads once if embedded
- Defaults are now defined on vars loaded (immediately if registered after) if a default is supplied and the saved variables are nil
- Added noAutoDefault param for options and option sets. If defined, option's value overrides set's values.

Hi, I am looking to convert from LibSimpleOptions to Portfolio but don't really want to rewite my whole addon to cope with all checkboxs returning 1 and 0 instead of a boolean. Is there a way to use boolean instead?

Originally posted by AnduinLothar
[b]LibDefaults is not especially big (12KB), which includes LibStub (1.33KB), but that's a lot smaller than Portfolio (92KB). Note that these are file sizes, which only really directly effects load time with parsing and processing. As for processor and memory overhead I haven't run any extensive testing.

The amount of extra code required trying to get around Portfolio doesn't seem to be worth it, I think I just have to figure out how to get CurseForge to package Portfolio with my addons, sucks to have to download 900k worth of duplicate libraries though

LibDefaults is not especially big (12KB), which includes LibStub (1.33KB), but that's a lot smaller than Portfolio (92KB). Note that these are file sizes, which only really directly effects load time with parsing and processing. As for processor and memory overhead I haven't run any extensive testing.

You can just embed or require LibDefaults if you want Portfolio to be optional and still want the benefits of LibDefaults. You will still have to delay callbacks until the saved variables load, but you can utilize LibDefaults' SetScript for that so you dont have to make your own frame or event handler.

Something like:

lua Code:

local optionTable ={

id ="evl_Test",

text ="Test",

savedVarTable ="evl_TestDB",

options ={

{

id ="OptionA",

text ="Option A",

type= CONTROLTYPE_CHECKBOX,

defaultValue ="1",

callback =function()print("Option A callback")end

},

{

id ="OptionB",

text ="Option B",

type= CONTROLTYPE_CHECKBOX,

defaultValue ="0"

},

}

}

local Portfolio = LibStub("Portfolio", true)

if Portfolio then

Portfolio.RegisterOptionSet(optionTable)

else

local addonName ="MyAddonName"

local LibDefaults = LibStub("LibDefaults")

--Init defaults

for _, option inipairs(optionTable.options)do

if option.defaultValue ~=nilthen

LibDefaults:SetDefault(addonName, option.id, option.defaultValue)

end

end

--Init Callbacks

local CallCallbacks =function()

for _, option inipairs(optionTable.options)do

if option.callback then

--args: value, isGUI, isUpdate

option.callback(evl_TestDB[option.id], false, true)

end

end

end

LibDefaults:SetScript(addonName, CallCallbacks)

end

Note: that "evl_TestDB&amp;#91;option.id&amp;#93;" should be "evl_TestDB[option.id]" but the highlighting ate it!

That would mostly emulate what Portfolio does. Except it assumes that you want all your callbacks called. If you use buttons or some other plugin control type that has a callback but not a current value then you'd need to modify your CallCallbacks function to ignore those (something Portfolio does automatically).

Originally posted by AnduinLothar That works, provided you make a frame and an event driver and run it on ADDON_LOADED. Otherwise your callbacks will always be fired based on the defaults and not on any changed saved variables.

Plus the way you have it written you're not passing the current values to the callbacks anyway, so that doesn't exactly mimic what Portfolio does, even if it works for your usage.

Also, some control callbacks are not meant to be called on init, like callbacks for buttons, which are only called when clicked.

Of course then if LibDefaults is not present you wont be able to reset to defaults and your saved variables will contain all unchanged defaults (which can increase loading time if there are a lot). But if you want to sacrifice that for the sake of addon size that's your call.

Aha so my implementation has some hitches

My point is, idealy I'd like people to be able to run all my addons without any libraries of any sort, but if they have said library they get a nice pretty GUI to play around with.

I'm not really sure how much overhead Portfolio adds other than static memory which isn't really a big deal I guess but it would be nice to know!

I have about 8-9 addons that are pretty small and I would hate to have to embed Portfolio in all of them.

That works, provided you make a frame and an event driver and run it on ADDON_LOADED. Otherwise your callbacks will always be fired based on the defaults and not on any changed saved variables.

Plus the way you have it written you're not passing the current values to the callbacks anyway, so that doesn't exactly mimic what Portfolio does, even if it works for your usage.

Also, some control callbacks are not meant to be called on init, like callbacks for buttons, which are only called when clicked.

Of course then if LibDefaults is not present you wont be able to reset to defaults and your saved variables will contain all unchanged defaults (which can increase loading time if there are a lot). But if you want to sacrifice that for the sake of addon size that's your call.

Check if LibStub and Portfolio are available (note the true parameter to LibStub which suppresses any error messages if it can't be fetched).

If Portfolio is present, register the options table normally.

Otherwise check if the SavedVariables table exists, if it doesn't create a new table.

Work through each option in the options table setting non-existing defaults (so values that were already written by Portfolio before the library was disabled/deleted are preserved) and calling each options callback.

I am struggeling with blizzard options for so long now. I have two addons, one is successfully writing the options to the saved variables, the other is not and I am not sure why. Therefore I have not published it yet, though it works fine for me. Both addons use the blizzard interface for customization and it is working fine so far, except making the values persistent.

I wanted to write my addon without any other 3rd party lib, but for your fine collection, I'll make an exception.

I'll come back and praise you again, when I actually have applied your lib to my addons )

Originally posted by evl I've been working on incorporating Portfolio into my small addons and I've run into some design problems.

Firstly I'm having problems using callbacks on other controls, an example I would like to see is a slider that when changed updates the value of a text control (I really think this should be defaut in slider as there is currently no way of knowing the sliders current value).

Secondly, the handling of defaults via LibDefaults is nice and all but it creates a tight-coupling between the addon and Portfolio since disabling Portfolio will cause the addon to stop working due to the LibDefaults dependancy, currently I've implemented normal defaults handling to get around this (MyAddonDB = MyAddonDB or defaults) but it's not very pretty.

Lastly, would it be possible for the about page to be optional? Currently I feel it's just bloat and adds an extra level to every panel.

It might be a good idea to have a separate text below the slider that shows its value, but the way I implemented it was generic to all options:
option:SetValue calls self:UpdateText which updates the option text to the value of Portfolio.Control.GetValueText(control, "text") which returns control.text unless the text value is a function or a format string.

So by setting the text = "Name (%s)" your slider text will include the value.

As for saved variables you have a number of options. Either make your addon depend on or embed Portfolio which includes LibDefaults. Or make your addon depend on or embed LibDefaults. Or you can optionally depend on both. If LibDefaults isn't available then you are obviously going to have to handle your own variables. It was done this way for flexibility. You can decided if you want to go to the extra effort to make Portfolio and/or LibDefaults optional.

As for the about page, yes I can make it optional. But I'm going to leave that decision until I revamp the paging to better handle sub-pages, which is on the todo list. In the mean time you can put useful info in your toc file that will get displayed there, like Version, Author, X-Category, X-Website, and X-Email

I've been working on incorporating Portfolio into my small addons and I've run into some design problems.

Firstly I'm having problems using callbacks on other controls, an example I would like to see is a slider that when changed updates the value of a text control (I really think this should be defaut in slider as there is currently no way of knowing the sliders current value).

Secondly, the handling of defaults via LibDefaults is nice and all but it creates a tight-coupling between the addon and Portfolio since disabling Portfolio will cause the addon to stop working due to the LibDefaults dependancy, currently I've implemented normal defaults handling to get around this (MyAddonDB = MyAddonDB or defaults) but it's not very pretty.

Lastly, would it be possible for the about page to be optional? Currently I feel it's just bloat and adds an extra level to every panel.

Support AddOn Development!

You have just downloaded by the author . If you like this AddOn why not consider supporting the author? This author has set up a donation account. Donations ensure that authors can continue to develop useful tools for everyone.