User tests: Successful: Unsuccessful:
Since version 1.5 Joomla made huge step to split HTML and PHP Logic, but JavaScript still in "younger brother" role, and very hard coded.
Here is a try to split PHP and JavaScript.
You ask: How that possible?
Very simple: Write PHP in PHP file, and JavaScript in JS file
You ask: Then how to connect them?
Well, using JSON, maybe it not the best solution but also not the worst.
So
This pull request introduce Joomla Behavior API
for client side.
It:
Also introduce JDocument::setScriptOptions()
that is important gear in connection betwen PHP Logic and client side.
Each Behavior could be called only if server provide the options for that Behavior, or always.
How it work.
Each Behavior it just a collection of callbacks that called when corresponding event happen.
All behavior could be stored in js files, and splitted by group: behavior-bootstrap.js
, behavior-form.js
so on.
Register custom behavior:
Joomla.Behavior.add('behaviorname', 'ready update', function(event){
console.log(event.name, event.target);
}, true);
This will be called only if Server provide options for this behavior $doc->addScriptOptions('behaviorname', array(/*some options*/))
, and when happened next events: ready
, update
Register custom behavior that will be always called:
Joomla.Behavior.add('behaviorname', 'ready update', function(event){
console.log(event.name, event.target);
});
You asked: wtf is update
event?
update
- is a custom event that allow to call one Behavior from another "on fly", also this event must be called if you replace part of DOM, so other Behaviors will know about changes.
There also remove
custom event, that could be used to ask Behaviors for clean up in 'container', unbind events and so on.
This events we could rename of course to something more clear, if need
And, of course, anyone could use any custom event that need for his/her needs.
Example:
// Call all behaviors when you change part of DOM
Joomla.Behavior.call('update', changedContainer);
// Call specific behavior by name when you change part of DOM
Joomla.Behavior.call('update.behaviorname', changedContainer);
// Call specific behavior by name when you change part of DOM, and with custom options
Joomla.Behavior.call('update.behaviorname', changedContainer, options);
// Request other Behaviors to "clean up in" container
Joomla.Behavior.call('remove', container);
How it work on Live you can look in repeatable field in repeatable.js (see diff)
How to override existing Behavior.
Very simple, just register own behavior with same name, and it will override any existing:
Joomla.Behavior.add('behaviorname', 'ready update', function(event){
console.log('I am override');
});
or you could override only specific event callback if need:
Joomla.Behavior.add('behaviorname', 'update', function(event){
console.log('I am override for Update event');
});
B/C
I tried make it for keep b/c, with count that it could be in the core since ~~3.~~5.0
So you should not see any difference after apply this patch.
Conclusion
Split PHP logic and JavaScript is a small step to big improve
Next step could be dependency management using Requirejs or anything else.
Well to much text, I hope here understandable at least couple words
ping @phproberto @dgt41 @okonomiyaki3000 @anibalsanchez I need your brains
ps. couple word about jquery
Labels |
Added:
?
|
about require.js, I would like step by step, not all at once in this case
Category | ⇒ | Components JavaScript Libraries |
This proposal is very similar to the current model, based on addScript/addScriptDeclaration. In both models, there is a Javascript declaration inside Joomla. I wouldn't rewrite everything just to introduce an alternative new semantic to declare and bind the same code in a different way.
As proposed in #1227, in my view, it is better to introduce a dependency management to declare modules. Based on RequireJS or not. For instance (like AngulaJS), Joomla could have Joomla.module: joomla.module(name, [requires], [configFn]);
// Create a new module
var myModule = joomla.module('myModule', []);
// register a new service
myModule.value('appName', 'MyCoolApp');
@anibalsanchez that is good argument
hm
I tried make it in simple way, if user use:
jQuery(function(){
// some code 1
});
jQuery(function(){
// some code 2
});
He "just" need to change it to
Joomla.Behavior.add('com_another.bla', 'ready update', function(event){
// some code 1
});
Joomla.Behavior.add('com_another.blabla', 'ready update', function(event){
// some code 2
});
And as advanced use, he could put all these in the one file eg com_another/js/behavior-another.js
, and set the Flag for each Behavior optionsRequired = true
, so each of these Behavior will be called only when it need.
Another thing I have no idea how to organize interaction between different Behaviors if use pure Requirejs module.
Example how could solve #1260 (comment) or more real problem (that could be solved more simple in Behavior case see here)
To be honest I have some doubt about requirejs, it is good for personal app, but not very imagine how it could live in CMS where everyone do what they like.
But it can be because lack of knowledge, I do not have big Requirejs experience.
For future, my thought was to use each Behavior file as Requirejs module, instead of each Behavior as Requirejs module, something like:
require.config({
baseUrl: 'media',
paths: {
'joomla': 'system/js/core',
'jquery': 'jui/js/jquery',
'bootstrap': 'jui/js/botstrap',
'behavior-bootstrap':'jui/js/behavior-bootstrap',
'behavior-another':'com_another/js/behavior-another'
}
});
require(['joomla', 'behavior-bootstrap', 'behavior-another']);
// in behavior-bootsrap.js
define('behavior-bootstrap', ['jquery', 'bootstrap'])
// in behavior-another.js
define('behavior-another', ['behavior-bootstrap'])
In this case we still need: or load all available paths; or think about how we could count it automatically.
@dgt41 if current suggestion looks better, then yes
@fedik @anibalsanchez All require.js and #1227 is needed, at least from my perspective, is one function that will automatically pick the correct path, something similar to what JHTML::_('script' 'bla-bla'); does. So we still do some pre-processing for the paths in php and then hand over the paths to require.js…
The argument of @anibalsanchez for introducing yet another similar model is valid, but I also see that an abstraction will work nicely for many transitions e.g. Bootstrap or any other framework. The hard part in this proposal is that the learning curve increases a lot, and that is also something we have to consider.
To split PHP and JavaScript: behavior-based events are still a Javascript (JQuery) declaration in PHP to manage files. Dependencies (and conflicts) can only be solved at DOM level, where browsers can do a better job than an abstraction on Joomla side.
In my view, instead of develop a new "loader", the Javascript standard way to manage files is module management (with named dependencies).
Concerning to modules: At this time, Javascript modules are just around the corner with the upcoming ES6 support. http://www.2ality.com/2014/09/es6-modules-final.html
Beyond the specific dependency manager (RequireJS, joomla.module or ES6 imports) , do Joomla have a need to improve well-written encapsulated functions (e.g. JQuery plugins)?
In my experience, working with RequireJS, I have not found any extension working with modules. You can only find a module, if (by chance) a developer included a file with an alternative initialization detecting AMD ( typeof define === "function" && define.amd ).
My personal opinion is that Joomla can add features to support Javascript Apps and modules. It is not clear if widgets or regular ajax routines can take advantage of techniques oriented to 100% Javascript projects.
Working with RequireJS on Joomla: I have been working with RequireJS since #1227, integrating modules with Backbone.js, Underscore.js and AngularJS (I just can't choose which one is better ;-) ). In a regular view, a Javascript App is declared in this way:
$file = MyLoader::getRelativeFile('js', 'com_todo/todo.min.js');
$dependencies = array();
$dependencies['todo'] = array('mycore');
MyLoader::initApp(MY_VERSION, $file, $dependencies);
The Loader can work in two modes to generate the Javascript loading routine.
Module Loader - Browser mode
var deps = [], paths = [];
deps.push({key:'todo', value:{deps: ["mycore"]}});
deps.push({key:'backbone', value:{deps: ["underscore"]}});
deps.push({key:'mycore', value:{deps: ["backbone"]}});
paths.push({key:'todo', value: 'media/com_todo/js/todo.min'});
paths.push({key:'underscore', value: 'media/lib_todo/js/backbone/underscore.min'});
paths.push({key:'backbone', value: 'media/lib_todo/js/backbone/backbone.min'});
paths.push({key:'mycore', value: 'media/lib_todo/js/mycore.min'});
</script>
</head>
<body> ..
<script type='text/javascript'>
var allKeys, config = {
baseUrl: 'http://.../',
urlArgs: 'bust=7.7.0',
shim: {},
paths: {}
};
// Additional deps
jQuery.each(deps, function(index, dependency) {
config.shim[dependency.key] = dependency.value;
});
// Additional paths
allKeys = [];
jQuery.each(paths, function(index, path) {
allKeys.push(path.key);
config.paths[path.key] = path.value;
});
// Require.js allows us to configure shortcut alias
require.config(config);
require(allKeys, function() {
});
</script>
</body>
Module Loader - Server mode
<script src="...media/lib_todo/js/require/require.min.js"></script>
<script src="...media/lib_todo/tmp/d97719a869966d81c4a1234c436b4250.js?7.7.0" ></script>
<script type='text/javascript'>
require.config({
baseUrl: "http://..../"
});
require(["todo","mycore"], function() {
});
</script>
Additionally, it is simpler to just add a way to declare: JHtml::('foundation.framework'), JHtml::('ink.framework') etc
well, theory sounds good, what about practice?
Then,
how we could replace hardcoded javascript in JHtml
(example1, example2 .. there a loot more)
And how, in this context, we could solve example that I worte above: #1260 (comment) and that(solution in suggested pull)
And remember, we talk about CMS (where user use extensions from different developers) and not about personal App (where you do all by yourself).
The hard part in this proposal is that the learning curve increases a lot
hm, I tried make it simple as possible,
maybe I really did something wrong
or my description very bad
There are two discussions going on:
Current API already supports any framework. No need to introduce a new syntaxis binded to events. We already have JHtml and JLayouts to load view-related code in an overridable way.
In Joomla, there is no Javascript coordination. All Javascript declarations and files are loaded in the head, in the same order than executed code.
In general, there is no way to know (server-side) if two scripts are going to have a conflict. An API can try to coordinate events under a simplified Javascript model.... a JBehaviour.... It can work for some time, but Javascript evolves independently than this semantic. Thus, it is unkown if it is going to support the next Javascript iteration. We would have to talk with JBehaviour to generate Javascript code in some way... and work around limitations.
A dependency manager does not even try to solve DOM events conflicts or coordinate dependencies by event. It relays on the developer criteria to declare "a before b and c".
Particularly, Joomla.Behavior is not a loading manager. It is a scheduler, with a list of event-associated tasks. The API allows to "add", "update" or "remove" tasks. In a shared space, if update or remove are allowed, all extensions can compete to be the only one to execute. If only "add" is allowed, a serialization can be performed. It is going to be fair, but it may not solve DOM conflicts caused by automatic code generation.
PD: Sorry for the long post ;-) I tried to present these ideas in an orderly way.
well, yes, I tried fix a couple problem
about PHP, two thing:
JHtml
not possible override without tricky hack
$doc->addScriptDeclaraion(JLayoutHelper::render('bootstrap.alretjs'))
, do not ask me why about coordination:
I had some problem with this, and I think it should be in CMS,
And from my point of view it is only one possible way to connect different extension between each other, on the client side.
I do not agree with:
It can work for some time
in Drupal It work well for years, of course they have some limitation, example each behavior they save in Object, so there no way to "reorder" existing behaviors and it always called alphabetically ...
One of my first try was very close to their solution ...
about dependency, I did not tried to fix it in current pull, but I thought it could be more simple after
you know that JHtml not possible override without tricky hack
I have to object to this statement. Overriding jhtml is perhaps not as simple as it could be but its not tricky and it's absolutely not a hack.
@okonomiyaki3000 look on JHtml
from point of view the template developer, and example, you need change options for JHtmlBootstrap::popover
, but you cannot as it already loaded somewhere else
In this case, create the plugin that will load custom JHtmlBootstrap
before everything else is a tricky hack ... from my point of view
@Fedik I know what you mean but I disagree especially with the word 'hack'.
I don't think developing a plugin is any trickier than developing a template. A plugin that would override some JHtml
functions could certainly be simpler than a full template. Still, it's a little different and someone who has never made one before might feel a little apprehensive about it. I do think Joomla! should have more of a 'drop-in' style approach to JHtml
overriding.
But using the word 'hack' here is completely wrong. Overriding JHtml
functions can not ever be considered a hack for the simple reason that JHtml::register
exists. The class has a public function specifically for the purpose of overriding any of its keys. You can't really get any more un-hack-like than that.
well, it just a question of the determination
No. The word "hack" actually means that you need to something that is unorthodox or generally not recommended. A "hack" implies that there's a good chance that your code will not work in certain edge cases or that a change to the core libraries that shouldn't affect bc in any way will break your code.
you just confirmed that it is hack
, because override the core
class is not recommended as it could lead to unexpected b/c issue in future
Sorry, I can't tell if you're kidding or not. There is no need to override any class.
ok, maybe I just not know something ...
Jhtml::register(). Look it up. See what it does.
hm, you are right, I missed part that you can register function for specific key
Hi @Fedik, I have been reading the code in detail ... I am not convinced that PHP and JavaScript has to be decoupled in browser with Joomla.Behavior.
Until now, PHP and JavaScript are always declared together. It is auto-documented. You can read the code and understand how it works. Now, with Joomla.Behavior, you have to follow the execution path, via the associated event, to reach the final routine.
Furthermore, to override a behaviour, you have to talk with Joomla.Behavior. On browser side, if anything goes wrong (a buggy behaviour or anything else), you could end with a general broken execution. Similar to a broken Joomla.submitbutton. In this case, it is not so frequent on front-end site, but I guess Joomla.Behavior is headed to solve the multi-framework support (bootstrap3, foundation, etc).
On the server-side, keeping PHP-Javascript tight, e.g. $doc->addScriptDeclaraion(JLayoutHelper::render('bootstrap.alretjs')), it is easier to read, follow, and override. If anything goes wrong on browser Javascript execution, it is a different conflict than the render process that generated the page.
Joomla.Behavior can help to solve the case for event coordination. But, I think it should not try to also solve the need to decouple PHP and JavaScript.
About exception and debug.
if exception happen in Behavior that in the file, it will be like: error in behavior-bootstrap.js:30
,
if the script in the body, then it will be: error in /current/page.html:126
For me more simple have a deal with first case, because I know that something went wrong in behavior-bootstrap.js
and so I do not need to search what PHP code generate the HTML with error (for second case)
You can read the code and understand how it works
hm, that was my point to move in JS file, example code highlight works better there
and it is why I not fan of $doc->addScriptDeclaraion(JLayoutHelper::render('bootstrap.alretjs'))
But maybe it more personal preferences.
Also there already was some requests to avoid javascript in the body, but not remember details
About override
It happen itself, when someone call Behavior.add()
with behavior name that already exists.
Also if it in the one file, then could be overridden whole file, because JHtml::_('script').
Another good thing
Hope you already noticed how cool JDocument::addScriptOptions()
?
at least from my point of view
It allow override existing options, example:
// this added in some core or component layout
$doc->addScriptOptions('bootstrap.popover', array(
'.popover-selector' => array('position' => 'top')
));
// but you want different position in your template, then you just do:
$doc->addScriptOptions('bootstrap.popover', array(
'.popover-selector' => array('position' => 'right')
));
// so it will override options for existing selector
that not really possible in case '$doc->addScriptDeclaraion(JLayoutHelper::render('bootstrap.alretjs'))' ...
hm, well, it is possible if inside the layout you will use Joomla.getOptions()
or Joomla.optionsStorage
some note about core.js
, after formating there difficult to see what new:
all new thing I added in the end of the file
Title |
|
Status | Pending | ⇒ | Closed |
Closed_Date | 0000-00-00 00:00:00 | ⇒ | 2016-05-20 15:27:38 |
Closed_By | ⇒ | roland-d |
Everybody, a great discussion but we can't seem to reach an agreement as to how to move forward with this. I am going to close this now but feel free to re-open if you plan to continue working on this.
Thank you for your cooperation.
Labels |
Added:
?
|
Labels |
Added:
?
|
Going to reopen so it will be seen even though it is a reevaluate fod 4
Status | Closed | ⇒ | New |
Closed_Date | 2016-05-20 15:27:38 | ⇒ | |
Closed_By | roland-d | ⇒ |
Status | New | ⇒ | Pending |
@mbabker for [4.0]-Projects?
Title |
|
Title |
|
Title |
|
Category | Components JavaScript Libraries | ⇒ | Administration com_content com_modules JavaScript Templates (admin) Libraries Front End Templates (site) Unit Tests Components |
@dgt41 I need some input here, what do you think
I have made possibility that the behavior can be loaded on the page all the time, but called only when the scriptOptions
exists for this behavior.
Joomla.Behavior.add('behaviorname', 'ready update', function(event){
console.log(event.name, event.target);
}, true <===);
I think about change it to:
Joomla.Behavior.add('behaviorname', 'ready update', function(event){
console.log(event.name, event.target, event.options);
}, 'script-options-key-blablabla' <===);
it allow to put the behaviors in a single file, load it to the page, and they will be executed only if scriptOptions
contain script-options-key-blablabla
.
Or I just remove such possibility?
So the ready event corresponds to document.('DOMContentLoaded') ?
What happens if scriptOptions not there? Crash and burn?
So the ready event corresponds to document.('DOMContentLoaded') ?
yes right
What happens if scriptOptions not there? Crash and burn?
blue screen with random bright yellow words on it, obviously
well, no
let say you have behavior do-stuff
in my-cool-template.js
, which loaded with all your scripts, but should be called only if the options there:
Joomla.Behavior.add('do-stuff', 'ready update', function(event){
console.log(event.name, event.target, event.options);
event.target.style.background = event.options.background || '';
}, 'do-stuff-options-key');
for make it run you
OR add the options via PHP (example in template):
$doc->addScriptOptions('do-stuff-options-key', array('background' => 'black'))
so at time of DOMContentLoaded
Joomla.Behavior will look at scriptOptions, and if there a do-stuff-options-key
, then it will call your behavior.
OR can call it via your another script (or from another extension), with your custom options:
var changedContainer = document.getElementById("main-container")
Joomla.Behavior.call('update.do-stuff', changedContainer, {background:'yellow'});
if there no options at time of DOMContentLoaded
(was not added by PHP), then your behavior do-stuff
will be ignored.
Category | Components JavaScript Libraries Administration com_content com_modules Templates (admin) Front End Templates (site) Unit Tests | ⇒ | Administration com_content Templates (admin) JavaScript Front End Templates (site) Components |
Status | Pending | ⇒ | Closed |
Closed_Date | 0000-00-00 00:00:00 | ⇒ | 2017-05-14 16:59:19 |
Closed_By | ⇒ | Fedik | |
Labels |
Removed:
?
|
Now I can see how bootstrap 3 can be implemented without breaking B/C (at least for the js part)!
Excellent proposal, but I will need some time to get my head around :)
Also can we try to reopen #1227 for require.js? @anibalsanchez?