@rfindley I am reporting on the work I have made for the html type setting. I have been successful beyond my wildest expectations. This will be long. I have a lot to report on.
I have made changes to the Settings.js
module and to the css. I have posted the changes on github. For css I didn’t change any existing rule. I just appended new rules at the end of the file to style html type settings. More about this below.
I have prepared a test set to let you verify how what I did works. This will come handy to test changes. You are a much better programmer than I am. I expect that you will find things to improve on and disagree with some of what I did. This is part of the process. I will adapt to what you say.
The test set is on greasyfork.
The test set does two things. First it installs this.
It will open a dialog where a series of tests for html type settings are displayed. I have done a lot of testing to make sure my proposal is complete, robust and doesn’t break anything.
The other thing is it install a series of test filters. I am interested in html types for developing filters so I tested that I can successfully write a filter.
I have updated the test version of Self-Study Quiz I have supplied you to support html type filters. You may use it to verify the test filters. It is on greasyfork.
As an aside I no longer intend to include all filters in @require
directives. I made some research on how these directive work and I didn’t like what I have found. I will load the filters with wkof.load_script()
instead. If you are interested I will provide you this code for Self-Study Quiz when it is ready.
Here is an account of what I have done.
Insertion of html types in the dialog
I have modified the code for inserting html type element to this. This is in the config_to_html(context)
function.
case 'html':
context.config_list[name] = item;
var content = item.content;
for (cname in content){
context.config_list[cname] = content[cname];
populateDefaults(context, base, cname, content[cname]);
}
html = make_label(item, item.hover_tip);
html += wrap_right(item.html);
html = wrap_row_html(html, name, item.shade);
I have found that including the html in a standard (for the dialog) .row .left .rigth
series of div works better than in-line insertion. It sets aside dialog screen estate in a manner that is consistent with the rest of wkof. It is a bit easier for the developer to craft html that doesn’t break the overall layout of the dialog. But the most important advantage is it creates a structure that css rules and Javascript code can rely on. There will be some examples below. The alternative would be to impose some standard on how the html is crafted, but if this is required it is better to automate it.
The function wrap_row_html()
is like this:
function wrap_row_html(html, name, shade) {return '<div class="row full html_type'+(shade?' html_shade':'')+'" name="'+name+'">'+html+'</div>';}
- The class
html_type
identifies the row as an html type setting. It is used for limiting the scope of css rules to html types.
- The
html_shade
class is used to create an optional border around the inserted html and set the background color to make the whole html setting standout visually.
- I force the row to be full width. I toyed with the idea of making it optional but I couldn’t find a html layout that makes sense in this configuration. This is not a real limitation. The crafter of the html can make a half width layout if they want.
- The name attribute is defined in the row because Item Inspector and Self-Study quiz need to locate the name of the setting when initializing or toggling the enabling checkbox of a filter. They currently do this with
row.find('.setting')
.but in an html element there is no guarantee that there is an element with class setting
. A html filter may take no parameter and display only text. Storing the name in the row guarantees that there is a place to find it.
The make_label(item, item.hover_tip)
has been modified to take an extra hover_tip parameter. We can place a hover_tip on the label of a html type setting. The new make_label()
is functionally equivalent to the old one when the hover_tip parameter is absent.
The html insertion code also populates context.config_list
with configuration subobjects. When testing tagging I have found that the code all over the place expects to find a configuration subobject for the item being processed in context.config_list
.
This applies to the individual settings that comprise the html type element. A configuration subobject for the whole html type doesn’t do because each setting is tagged with a different name and context.config_list
is accessed by this name…
I didn’t want to make the code work in absence of a configuration subobject. I preferred to supply the data. I require each html type to come with a context:
subobject that contains the individual configuration for each setting and button that must be managed by the framework. This is the purpose of the for (cname in content)
loop in the html insertion code. No html is generated from this. It just make the code happy. And it works.
I also add the configuration subobject of the whole html in context.config_list
. This is needed to support pre_open
callbacks for filters. There is in the test filters a real-life example of a filter that needs it. More about this later.
The configuration subobject may contain a default value for the subobject. The for loop initializes this as well. I have moved all the default initialization code of config_to_html(context)
in a separate function named populateDefaults()
and call this everywhere. This permits to call this code from the html insertion routine.
WKOF functionality in html type setting
The decision to require configuration subobjects has been hugely successful. Most of the time the individual setting is treated by the code the same (meaning the same code path) as if it were outside of an html type. This is because the data is in config_to_html(context)
so the code doesn’t see a difference between html and non-html type settings.
The following features work in html type settings with no change in the code. It is sufficient to request them in the configuration.
on_change' call backs
on_click' call backs
refresh_on_change
flags
path
I now realize that I forgot to test path
outside the context of a filter. But I have tested it within filters and it works.
The following features are ignored if they are set .
label
hover_tip
placeholder
full_width
These options are implemented by generating the html accordingly. Since there is no html generation for html type these features are ignored. This is not a real limitation. The crafter of the html may incorporate them by hand in their html.
Built-in validation and the validate callback work too but there was a snag. The error message was displayed below the whole html type element instead of below the individual setting that triggered it. This is because the code issue a elem.closest('.right')
call to locate the div below which the message should be displayed. In a html type this locates the .right
that encloses the entire html. My solution is that the class msg
should be used for this purpose. The validation code now issues a elem.closest('.msg').
I have modified wrap_right()
that is responsible to create .right
div to always include the msg
class so the new code is functionally equivalent to the old when used outside of an html type. But within an html type the crafter has the option to enclose his setting in a div of class msg
to position the error message at the correct place.
textarea
<textarea> types work great in this solution. Their configuration subobject uses type ‘others’ or ‘textarea’, it doesn’t matter which one. This falls under the default:
clauses of the relevant switch
statements where and'elem.val()
is called. This works as you intended. I didn’t believe it would but I misread your code. I am sorry about that.
The following wkof features work for textarea. If suffices to request them in the configuration. The reason is the relevant code does not test for item.type
. The code is executed regardless of the type.
validate
callback (with the change mentioned above)
on-change
callback
refesh_on_change
path
(tested only in filters)
Tagging
Tagging the settings is required as you indicated. This process is error prone and debugging may require to place traces in the wkof Setting module. There is no error message. The dialog just fails to synchronize with the data in the settings without giving a clue about what goes wrong. Debugging this is a hassle.
There are rules to follow. Any departure from the rules result in the dialog not synching with the settings. Reverse engineering what the rules are was painful and tedious. We must document them somewhere. Debugging the tags is much easier when we know what we must look for.
The rules are:
Any dialog element that needs to synch with the settings and every button that needs to use some wkof feature must have:
- The
setting
class set
- A name defined in the
name
attribute that is unique throught the entire dialog.
- The
id
attribute must have the form script_id_+'name'
where the script_id
is the same script_id
that is given in the global dialog configuration and ‘name’ is the same name that was given in the name
attribute.
- Labels
for
attribute must be set to the id
attribute of the input they correspond to.
- In the case of a filter the
'script_id
must not contain an underscore. Item Inspector and Self Study Quiz have to html.replaceAll()
this script_id
with their own script_id
when making their settings dialog to make this dialog compliant with the rules.
CSS
I have defined a number of classes for styling the html. It suffices to apply the class to an element and the relevant css is applied. I have found that without this crafters of the html would constantly reinvent the wheel. You will find a description of the classes and what they do in the HTML and CSS tests in both the Test Set dialog and in the test filters.
The classes provides the following:
- A look and feel that is consistent with the styling of setting dialogs.
- They make styling easy. it is just a game of applying the right classes.
- They cover the most frequent layouts.
- They are not mandatory. Crafters are free to add custom styling for special needs, or they may choose not to use them at all.
- They resize well.
- They handle the
.narrow
mode built into the current dialog styling.
- All css selectors are guarded with a reference to the
html_type
class that is inserted in the top .row
div of every html type. This ensures they don’t spill over unintended elements.
- No modification to the existing css rules of the framework were made. Conflicts were resolved by making the html type rule more specific. This makes for rather long selectors.
Filters
Html type usually have more than one setting but filter related code assumes there is only one. This include wkof.itemData.get_items()
that accepts a configuration object as input and passes the filter value to filter_func
and filter_value_map
. But wkof.itemData.get_items()
is a passthrough. It doesn’t care about the filter value datatype. I chose to arrange the html type filters to be mapped with a path
to the root of an object and all the individual settings are properties of this object. This enable to pass all the settings through wkof.itemData.get_items()
without changing this code. For wkof.itemData.get_items()
an object is a single data as is expected. It works.
In Item Inspector and Self-Study Quiz html type are handled with this code.
case 'html':
flt_content[src_name+'_flt_'+flt_name] = {
type:flt.type,
label:flt.label,
html:flt.html.replaceAll(/\sid=".*?_/g, ' id="'+scriptId+'_').replaceAll(/\sfor=".*?_/g, ' for="'+scriptId+'_'),
path:'@ipresets[@active_ipreset].content["'+src_name+'"].filters["'+flt_name+'"].value',
hover_tip:flt.hover_tip,
pre_open:flt.pre_open,
content:flt.content,
};
settings.filters[flt_name].value = {};
for (var cname in flt.content){
settings.filters[flt_name].value[cname] = flt.content[cname].default;
flt.content[cname].path = '@ipresets[@active_ipreset].content["'+src_name+'"].filters["'+flt_name+'"].value.'+cname;
};
break;
This is replicating the options in the registry into the dialog configuration subobject.
- The paths must be tailored to handle presets. They are not set by the filter.
- There is a path for the whole html which is used to send the settings to
wkof.itemData.get_items()
.
- There are individual paths for each setting that are used to synch the dialog and the setting.
- Default values are gathered for initialization.
- The html is
replaceAll()
to include the correct script_id
in the tags to comply with tagging rules.
There were some changes to the Item Inspector and Self-Study Quiz css. These rules are meant to style non-full width one setting filters controled by a checkbox. They interfere with the styling of full width multiple settings html types. I have guarded these rule with negative selectors on the html_type
class to remove the inteference.
I have added one css rule to force the label of a html type to stay on the same line as the checkbox and occupy the full width of the line.
I have written a real life filter that I wanted to do. The goal was to make sure I could actually use what I came up with. The filter is like this:
This is an Item List filter with a better user interface. The item lists are in textareas for ease of editing and users can save their lists in a file and read them back later. This enables users with many lists to swap them without having to retype them afresh.
One snag is that the filter has no knowledge of the path where the data is in the settings. This is determined by the sript that uses the filter. There is a workaround. Upon upload of a file I set the content of the textareas with an elem.val(list)
call. Then I force synching with the setting with an elem.change()
. The framework built-in onchange()
handler does the rest.
Then there is the download. The button is actually a link with a data:
url. I have on_change
handlers that update the href
attribute every time there is a change. This works excepts for one thing. When the dialog is first started the link is not initialized because there has been no call to an on_change
handler yet. For this I need a pre-open
call back.
I know there is a download API that I could use. I have rejected it because this is not supported in Safari.
It is not possible to use the global pre_open
callback because the filter is not setting up the dialog. A filter specific callback should be supported by the framework, otherwise I am out of luck. The filter users will have to make a spurious edit to initialize the download.
My solution is to support a pre_open
callback in the item configuration on top of the global one, I have introduced this code in the open(contect)
function just after the regular pre_open
call.
for (var name in context.config_list) {
var item = context.config_list[name];
if (typeof item.pre_open === 'function'){
var base = 'wkof.settings["'+context.cfg.script_id+'"]';
var path = (item.path || base+'["'+name+'"]');
path = path.replace(/@/g, base+'.');
var elem = $(dialog).find('div[name="'+name+'"]')[0];
item.pre_open(elem, path);
};
};
This iterates over context.config_list
to locate item specific pre_open
callbacks and call them.
There is another snag. pre_open
occurs before the dialog is refreshed. This is how it should be because we don’t know how pre_open
will affect the settings. I can’t get the information from the dialog because it is not initialized. I need the path to get the information from the settings. This code fully evaluate the path and passes it as a parameter. Then I have all I need to initialize the download link.
This concludes this rather long explanation. I hope I have forgotten nothing.