User tests: Successful: Unsuccessful:
Pull Request for Issue # .
Refactored the code into a function, and added events handlers for DOM ready and joomla:updated. In order to allow the code to be re-entrant, need to remove the options after BS init has been run, so we don't add multiple event delegations.
Also tidied up a few missing arguments, unused variables, and changed some 'var' to 'let'.
Basic testing is to load a page with Bootstrap widgets on it, like anything with tabs on the backend, and observe that everything still works. Testing the joomla:updated event isn't really possible, as it's designed to allow adding new forms to the DOM on the fly. You can prove that the code runs by putting a breakpoint somewhere in the _initBootstrap function, then fire the event in the console with ...
Joomla.Event.dispatch(document, 'joomla:updated')
... and you'll hit your breakpoint. But without adding a new form to the DOM and adding the options for it, which is hard to provide a test case for, that won't actually prove it works. But it does. My extension (Fabrik) uses this extensively, and I can confirm it does work.
Status | New | ⇒ | Pending |
Category | ⇒ | JavaScript |
Thanks for the feedback. And indeed. that's how I thought joomla:updated was supposed to work ... but if you look at the original bootstrap init code, it's not getting the Bootstrap objects to build from the DOM, it's getting them from the options. Well, except for the modals.
So in order to get it to work with the "initialize a container" way of working, we'll have to flip that around, so instead of each()'ing through the options and applying the BS to the option key, with any added BS options coming from the option values ... like it does now ...
let accordion = Joomla.getOptions('bootstrap.accordion', null),
...
if (accordion) {
$.each(accordion, function(index, value) {
$('#' + index).collapse(
{
parent:value.parent,
toggle:value.toggle
}
).on("show", new Function(value.onShow)())
.on("shown", new Function(value.onShown)())
.on("hideme", new Function(value.onHide)())
.on("hidden", new Function(value.onHidden)());
});
Joomla.loadOptions({'bootstrap.accordion': null})
}
... we'd have to find each of the Boostrap constructions in the DOM target, then find the specific options for that id (or class, depending on the "thing", like popovers use class, not id) ...
let accordion = Joomla.getOptions('bootstrap.accordion', null),
...
$(target).find('.accordion').each(function() {
let $self = $(this);
if (accordion.has($self.id)) {
let value = accordion[$self.id];
$('#' + index).collapse(
{
parent:value.parent,
toggle:value.toggle
}
).on("show", new Function(value.onShow)())
.on("shown", new Function(value.onShown)())
.on("hideme", new Function(value.onHide)())
.on("hidden", new Function(value.onHidden)());
}
}
So would that be the right approach?
I did consider it, but it seems a little cumbersome ... as we already have all the objects we need to BS-ify in the options, to then add DOM-aware code that needs to know the selectors for every structure it is working with ... and as this script is (afaict) the only one that references those bootstrap.foo options, and ONLY for the purposes of init ... and there is precedence for doing it this way, of disposing of the options after initializing, it's how jtext does it ...
https://github.com/joomla/joomla-cms/blob/staging/media/system/js/core-uncompressed.js#L121
... I thought I'd give it a go this way. But I'm cool with whichever way you want it done.
As for loading the new options - yes, my comment was assuming the app has added new BS options in the AJAX loaded content. So I just wanted to get that documented somewhere. So is ...
\JFactory::getDocument()->getScriptOptions()
... the officially blessed way of doing that?
BTW ... in dicking around trying to get this to work with AJAX added content, I found that using the embedded script tag with type="application/json" for new options was actually kind of difficult, because I'm having to parse scripts in the returned AJAX (I'm using requirejs to load dependencies). Which means I can't embed the JSON in a script tag, as that then gets parsed as a script, and blows up.
What I wound up having to do was the rather nasty way of adding a script to load the options and fire joomla:updated as I'm building the AJAX content on the server side ...
$scriptOptions = \JFactory::getDocument()->getScriptOptions();
if (!empty($scriptOptions))
{
$jsonOptions = json_encode($scriptOptions);
$jsonOptions = $jsonOptions ? $jsonOptions : '{}';
HTML::addScriptDeclaration("Joomla.loadOptions(JSON.parse('" . $jsonOptions . "')); Joomla.Event.dispatch(document, 'joomla:updated')");
}
... although once we get this init code "containerized" I'll have to change that dispatch().
Am I missing something obvious with the JSON script tag method?
hm, I see, it relay to id
everywhere
well, but still, your example:
if (accordion) {
$.each(accordion, function(index, value) {
$('#' + index)
.collapse({parent:value.parent, toggle:value.toggle})
.on("show", new Function(value.onShow)())
.on("shown", new Function(value.onShown)())
.on("hideme", new Function(value.onHide)())
.on("hidden", new Function(value.onHidden)());
});
}
will become:
var $target = $(target);
if (accordion) {
$.each(accordion, function(index, value) {
$target.find('#' + index)
.collapse({parent:value.parent,toggle:value.toggle})
.on("show", new Function(value.onShow)())
.on("shown", new Function(value.onShown)())
.on("hideme", new Function(value.onHide)())
.on("hidden", new Function(value.onHidden)());
});
}
Without resetting of Joomla.loadOptions({'bootstrap.accordion': null})
.
Maybe use a class selector would be more smart, but we have what we have
and there is precedence for doing it this way, of disposing of the options after initializing, it's how jtext does it
I think jtext
a bit more special, because jtext
store all strings in own storage. so it just to avoid confusion (where the strings is located) and to save a little bit of memory
In case of other scripts I would not do it, unless a script require it itself. it allow to other scripts to check which bootstrap
options are active/available, and if you reset them then it become more complicated.
BTW ... in dicking around trying to get this to work with AJAX added content, I found that using the embedded script tag with type="application/json" for new options was actually kind of difficult, because I'm having to parse scripts in the returned AJAX
The main point of use <script type="application/json">
for options is to avoid an inline scripts, and still be able to set JavaScript options from PHP.
I agree that can be a bit too complicated, when it come to AJAX.
In theory you have at least 3 way:
&tmpl=component
request, then just include result to document, without parsing, and just call Joomla.loadOptions()
, a new options will be parsed and merged automatically<script type="application/json" class="joomla-script-options new">{"myStuff":[1,2,3]}</script>
to your response HTML (it not required to be in <head>
), and then, as in previous case, in ajax callback just call Joomla.loadOptions()
, all new options will be parsed and merged automatically.<script type="application/json">
, they can be multiple.Joomla.loadOptions(jsonResponse.options)
your example:
$scriptOptions = \JFactory::getDocument()->getScriptOptions();
if (!empty($scriptOptions))
{
$jsonOptions = json_encode($scriptOptions);
$jsonOptions = $jsonOptions ? $jsonOptions : '{}';
HTML::addScriptDeclaration("Joomla.loadOptions(JSON.parse('" . $jsonOptions . "')); Joomla.Event.dispatch(document, 'joomla:updated')");
}
I would do (if you do not use &tmpl=component
):
$response = '<div>blabla html</div>';
$scriptOptions = \JFactory::getDocument()->getScriptOptions();
if (!empty($scriptOptions))
{
$jsonOptions = json_encode($scriptOptions);
if ($jsonOptions) {
$response .= '<script type="application/json" class="joomla-script-options new">' . $jsonOptions . '</script>'
}
}
Then client side:
function ajaxCallback(responseHtml) {
let container = ... get your container element
Joomla.Event.dispatch(container, 'joomla:removed');
container.innerHtml(responseHtml); // set new HTML
Joomla.loadOptions(); // parse new options
Joomla.Event.dispatch(container, 'joomla:updated');
}
Popover and tooltip use a class rather than individual ids. Well, more correctly I guess they use the selector as-is, without prepending the '#', and it happens to be a class (like .hasPopover).
https://github.com/joomla/joomla-cms/blob/4.0-dev/media/system/js/bootstrap-init.js#L104
OK, I shall redo the PR this way.
Thanks for your time and patience.
-- hugh
Thanks for your time and patience.
and thank you for your contribution
Labels |
Added:
?
|
OK, I think that's now doing it the right way.
Here's a screencast of it working ...
https://www.screencast.com/t/VFNQdQDDRH
Sorry about the Flash.
BTW, would it make sense to add that code that builds the JSON script tag as a helper in J! somewhere? I just copped that code from the StyleRenderer class and stuck it in my own helper, but if we're going to be expecting extensions to add that to AJAX output, it'd make sense to have a function that builds it.
looks good to me,
@cheesegrits thanks!
would it make sense to add that code that builds the JSON script tag as a helper in J! somewhere?
maybe not a bad idea, but to be honest I have no idea where
Ok couple notes from me:
The code here is ok, btw!
@dgt41 - what's the status on the core UI work? Last activity on that PR was in August.
I'm actively working on getting a very large component (Fabrik), which has a massive amount of back and front end UI, moved to 4.0. I'm more than happy to help out testing the new core UI features, but it'd need to be in the 4.0-dev branch.
Title |
|
Title |
|
@cheesegrits Can you update this PR or comment on the status, perhaps it can be closed or updated according the discussion in here?
It is two years since the last significant comment. Is anything happening with this?
@Fedik Is this still valid or should be closed? It appears @cheesegrits is no longer active.
many things changed since these 2 years
hard to say,
the file seems still in use
joomla-cms/libraries/cms/html/bootstrap.php
Lines 165 to 170 in 77a6643
I guess it still valid pull
Due to inactivity I will close this PR, but it can always be reopened in case someone wants to continue working on this.
Status | Pending | ⇒ | Closed |
Closed_Date | 0000-00-00 00:00:00 | ⇒ | 2020-08-01 15:26:44 |
Closed_By | ⇒ | roland-d | |
Labels |
Added:
Conflicting Files
?
|
@cheesegrits thanks for doing it
Here is a couple mistakes which need to fix.
I think it from misunderstanding of 'joomla:updated' concept.
Couple main mistakes:
Joomla.Event.dispatch(document, 'joomla:updated')
overdocument
(unless you changed whole<body>
), because this will lead do double initialization for some scriptsJoomla.addOptions();
, unless you really have loaded a new optionsThe event
joomla:updated
should be dispatched over the changed container, not over a whole document.Then other scripts should use
target
instead ofdocument
as starting point, that will prevent double initialization, see example 576a4b4#diff-6d7703e5e0a21c853fc1ee5549e9e79dR13Example:
On
DOMContentLoaded
the scripts will be applied to bothregular-content
anddynamic-content
. And when you change the content ofdynamic-content
block you need to dispatchjoomla:updated
.Example of AJAX callback:
Example of script initialization:
As you can see we dispatch the event
joomla:updated
overdynamic-content
, then the event bubbling up and catched byeventListener("joomla:updated", doRandomStuff)
. Andevent.target
is our updated containerdynamic-content
. So we work only with a new content and do not touch existing, this is prevent a double initialization.If you still have a questions just ask?