Aloha loading and building is too complicated #737

Closed
deliminator opened this Issue Oct 1, 2012 · 64 comments

Comments

Projects
None yet
9 participants
Contributor

deliminator commented Oct 1, 2012

A number of issues that I observed

If you try to make Aloha use the jQuery of the host page, and the host page uses jQuery UI, you get an obscure Javascript error. That's because Aloha's jQuery UI will modify the host page's jQuery - two jQuery UIs and a single jQuery instance can't work.

If you don't explicitly load jQuery as a script tag in the page, and set Aloha.settings.jQuery, Aloha will load its own jQuery asynchronously, and if the host page is loading a second jQuery asynchronously as well, I have no idea whether or not Aloha will end up with the correct jQuery (two jQuerys loaded asynchronously, both defining the same 'jquery' module - how does requirejs know which is which?).

If you want to load a plugin, you add it to the data-aloha-plugins attribute. If you want the plugin to end up in the concatenated and minified aloha.js, you modify the build profile. If you have a plugin listed in the data-aloha-plugins attribute but not in the build profile, it will be loaded asynchronously by requirejs. If you have a plugin listed in the build profile but not in the data-aloha-plugins attribute, you waste space since you don't use a plugin that has been concatenated into the minified aloha.js.

This mechanism is very unconventional. In my experience people don't expect code to be loaded dynamically by adding an entry to the data-aloha-plugins attribute, but they do expect a plugin to be loaded and activated by listing it in the build profile. I know of a developer that couldn't figure out why his plugin wasn't loaded even though it was right there in the minified file.

I suggest an alternate system that solves the issues above. The system I suggest will surprise everyone. It is a very conventional system.

<script src=".../jquery-1.7.2.js"></script>
<script src=".../jquery-ui-xxx.js"></script>
<script src=".../third-party-library-x.js"></script>
<script src="../aloha.js"></script>
<script src="../format-plugin.js"></script>
<script src="../link-plugin.js"></script>
<script src="../table-plugin.js"></script>
<script>$("#editable").aloha()</script>

We don't compile third party libraries into aloha. We carefully document the jquery, jquery-ui and other dependencies we need. No automatic loading of 3rd party libraries at all. We can't completely hide Aloha's dependencies. We are currently trying to do that, but that is creating problems since it hides obvious problems like including two different versions of jQuery UI with only a single jQuery instance in the page (which can't work since both jQuery UIs would try to mutate the single jQuery instance).

In the above solution, Aloha is split into aloha.js which contains the core, and one script file for each plugin. Plugins are automatically loaded when they are included. No data-aloha-plugins attribute.

We should not require requirejs in the built version. We can support it, but we shouldn't require it. We could use almond.js to provide the define() and require() definitions.

The call to aloha()/mahalo() should not have to be wrapped in a Aloha.ready(). Aloha.ready() should not be part of the public API. We can continue to expose Aloha modules (which have to be loaded synchronously in dev mode) via Aloha.require(...) (I'm assuming this works with almond.js).

If people want to optimize Aloha+Plugins into a single file, they can do

cat aloha.js plugin1.js .... > aloha-all.js

That's more obvious than fiddling around with aloha bundle configurations and requirejs build profiles, no?

I haven't yet exactly figured out how to do all that, so any ideas/suggestions are welcome.

I expect we would still be using requirejs for development and the building of the individual module files.

I certainly wouldn't mind loading my own jQuery(-ui). I don't think anyone would.

Contributor

wimleers commented Oct 2, 2012

OMG yes!

This would greatly simplify what we would need to do for Drupal integration as well. Also, when you're debugging things, require.js will nearly always cause problems, because it assumes there has been a network timeout, whereas you simply have been debugging.

While there's less handholding, there's also less magic that can make longtime JS developers stumble and go "WTF is going on here?"

Why would you still use require.js for development? Or do you mean for "building for development", not for "loading of all necessary files"? It's especially in development (and specifically debugging) that require.js is a PITA.

Contributor

deliminator commented Oct 2, 2012

While there's less handholding, there's also less magic that can make longtime JS developers stumble and go "WTF is going on here?"

My thought exactly.

To reach the goal of simplifying the aloha build/integration for the user, I would be prepared to use any tool. But, is there a better tool than requirejs?

Theodore found out during the Aloha+Drupal sprint that requirejs can be made to output one module per plugin, with the "exclude" configuration. It's a bit cumbersome to maintain, but we could write a little script that looks for errors like duplicate module definitions in the resulting modules.

Contributor

wimleers commented Oct 2, 2012

I've asked @theodoreb to comment here :) He's much more knowledgeable in this area.

Contributor

deliminator commented Oct 3, 2012

So, I think I have a good solution. I implemented two extensions to the build profle.

// If true, will output document.write(...) directives such that the
// original unconcatenated files are loaded, so they can be
// debugged.
//alohaBuildForDevMode: true,

// If set, only files under this path will be included in the
// optimized output file. Other dependencies will simply be skipped.
//alohaExclusive: "path/to/dir",

I've implemented these extensions by making use of the onBuildWrite callback function in the build profile.

The "alohaExclusive" property is an alternative to the "exclude" property that I mentioned earlier. The "exclude" property is a bit fragile: if you add an external dependency to a plugin (for example to ui/button) but forget to also put it in the "exclude" property, then you will get the external dependency compiled into your plugin. The "alohaExclusive" property on the other hand automatically excludes everything not under the specified path (which in our case are the plugin folders).

We still need to use the "include" property. But I think since we took care of the "exclude" property, it is acceptable. Maybe a solution for this would be to autogenerate a module file that contains all files under a certain path.

I've pushed the build profile and a demo (src/demo/boilerplate/index-modular.html) to the dev-building branch. The demo currently only works from the target folder where the compiled files are put. (This limitation can be easily fixed though by naming the define of each plugin).

The "alohaBuildForDevMode" property can be turned on on the command line like so

node build/r.js -o build/aloha/build-modular.js alohaBuildForDevMode=true

This will cause the output files to contain document.write(...) directives that load the respective files.

This isn't currently working though since the module files themselves are overwritten. But I think I can find a solution for that.

Contributor

deliminator commented Oct 4, 2012

Alright, development mode is now implemented as well.

I've renamed the branch from dev-building to dev-modular.

The demo in src/demo/boilerplate/index-modular.html will now work in development mode, as well as in compiled mode.

The demo contains *-module.js script includes. These files are compiled either in development mode or production mode. In development mode the files contains document.write(...) statements. In production the contain the concatenated dependencies.

I compiled the *-module.js files in development mode and committed them to the repository. They will have to be recompiled when dependencies change. Recompilation can be achieved with the following command (from the repository root)

node build/r.js -o build/aloha/build-modular.js dir=src alohaBuildForDevMode=true fileExclusionRegExp='.*'

The files will be put into the src folder, along side aloha source files. The fileExclusionRegExp is necessary so that r.js will only compile the *-module.js files and leave everything else alone.

To compile for production this command can be used

node build/r.js -o build/aloha/build-modular.js

This will put the output in target/build-modular.

Contributor

deliminator commented Oct 4, 2012

I very much like that both, during development and production we operate in completely synchronous mode and don't depend on requirejs.

This way the user doesn't have to know anything about asynchronous module loading, and everything will work the same in development mode as well as production mode.

Owner

cprerovsky commented Oct 4, 2012

Hey Toby,

I really like your idea of going back to good old script tags, since there is no learning curve and everybody will understand what happens and why it happens that way. That being said I'd like to challenge your approach with the reasons we introduced adm in the first place:

  1. Unfortunately there are some plugins that don't consist of a single JavaScript file. They have dependencies - like the table plugin, which consists of a whole bunch of files.
  2. Then there is the internationalization thing. How will those files be added automatically?
  3. And last there's css.

What I did not understand till this point: will a plugin be built by us every time we check in a new version? Do users need to add css files for plugins one-by-one?

Best regards,
Clemens

Contributor

deliminator commented Oct 4, 2012

The table plugins consists of several .js files. The autogenerated table-module.js contains a document.write() for each of those files. The table-module.js must be regenerated and committed to the repository when files are removed or new files are added.

i18n files are just modules. They can be loaded with requirejs dynamically (full requirejs compatibility!) or they can even be compiled into the final output file by specifying a particular locale at compile time. If a locale is specified, the appropriate document.write(..)s for the locale-specific files will be written to the table-module.js.

We already removed the css from all common plugins. No single common plugin loads css via requirejs. They are all included in the src/css/aloha.css with @import statements. We did this because we wanted a single optimized css file, not multiple unoptimized files injected by requirejs. I did discover some references that it may be possible to optimize css files with a different i18n plugin....

A nice sideeffect of having a single optimized css file is that it circumvents the IE maximum stylsheet limit, which is really annoying since you don't get an informative error message.

So, currently the user already has the possibility to include a single css file for the core and each plugin. If you really do want to inject css via requirejs in custom non-core plugins, that's still possible (full requirejs compatibility).

will a plugin be built by us every time we check in a new version?

The *-module.js files will have to be built every time dependencies change. So, if you add a new .js file to aloha, and add it to a 'define([...]' you have to invoke this command to rebuild the *-module.js files

node build/r.js -o build/aloha/build-modular.js dir=src alohaBuildForDevMode=true fileExclusionRegExp='.*'

The relevant *-module.js files will have to be committed to the repository.

Do users need to add css files for plugins one-by-one?

Currently, you have to include the aloha.css. It includes the css of every common plugin.
You also have the option of including only the css files you need individually.

Contributor

deliminator commented Oct 4, 2012

Here is a comparison between the current approach and my suggested approach (the css includes can optionally be reduced to a single include):

<link rel="stylesheet" href=".../css/aloha-core.css" type="text/css">
<link rel="stylesheet" href=".../css/ui.css" type="text/css">
<link rel="stylesheet" href=".../css/format.css" type="text/css">
<script src=".../aloha.js" data-aloha-plugins="
            common/ui,
            common/format
"></script>
<script>
Aloha.ready(function () { Aloha.jQuery('#editable').aloha(); });
Aloha.require(['ui/ui'], function (Ui) {
    Ui.show();
});
</script>
<link rel="stylesheet" href=".../css/aloha-core.css" type="text/css">
<link rel="stylesheet" href=".../css/ui.css" type="text/css">
<link rel="stylesheet" href=".../css/format.css" type="text/css">
<script src=".../aloha.js"></script>
<script src=".../plugins/ui.js"></script>
<script src=".../plugins/format.js"></script>
<script>
Aloha.jQuery('#editable').aloha();
Aloha.require('ui/ui').show();
</script>

It is much clearer to the user what happens with my proposed approach.

Greatest benefit in my opinion is that Aloha.ready() is a thing of the past. The user doesn't have to jump through hoops to support asynchronous module loading. This was an issue in another project that I'm working on - the existing codebase of the client didn't lend itself to the asynchronous approach.

Also, both dev mode and prod mode load in excatly the same synchronous order. Currently, dev mode is asynchronous and prod is synchronous, which problematic if you want to load Aloha in dev mode to debug a problem in an implementation that assumes synchronous loading order since the implementation was written with the prod (compiled) version of aloha.

As can be seen, the data-aloha-plugins attribute is a little less verbose, but doesn't solve any real problem, since you will have to list the plugins either way.

Contributor

wimleers commented Oct 4, 2012

Great progress!

Question: is Aloha.require() really necessary? I think what you'e doing there is getting the object corresponding to the ui/ui plug-in and then calling show() on that?


From a Drupal POV, this will make integration super trivial. In general, I'd say the loading magic is up to the web development framework to decide, not to a single piece of JS functionality. To this day, Drupal isn't using any loading magic, besides providing smart aggregation. However, there are plans to make it at least possible, but maybe even the default, to use a script loader such as require.js. It is infinitely easier to deal with individual JS files that have dependencies amongst each other than it is to either create a custom build or integrate some magic script loader that is closely bundled with the JS functionality you're trying to use (i.e.: Aloha Editor + require.js).

If you're interested, this is how we currently deal with it: http://drupalcode.org/project/aloha.git/blob/refs/heads/8.x-2.x:/aloha.module#l63 — as you can see, each dependency lists only "JavaScript settings" (things that end up in Aloha.settings), not actual JavaScript files. That is the end goal though, we need to get rid of Aloha's require.js. Each dependency must list its corresponding JS and CSS. Drupal can then do smart bundling and aggregating on all JS in the page, and optimize in ways that it could not do otherwise.

I completely understand the reasoning to make it as simple as possible for your users to get up & running with Aloha. But I also believe this is the right architectural choice: let your user choose his JS loading strategy!
:)

Contributor

deliminator commented Oct 4, 2012

Question: is Aloha.require() really necessary?

You need to get the reference to the "ui/ui" module from somewhere. Currently you can only get it through a call to Aloha.require. I think that's ok, since you can use it in the synchronous way. If we adopt my proposed approach, we can always use the synchronous call, without passing a callback, even in dev mode.

From a Drupal POV, this will make integration super trivial. In general, I'd say the loading magic is up to the web development framework to decide, not to a single piece of JS functionality.

Completely agree. Aloha is a library, not a framework.

I completely understand the reasoning to make it as simple as possible for your users to get up & running with Aloha.

The current mechanism doesn't even make it any easier. I worked with people integrating Aloha into several projects (Drupal and two others), and the asynchronous loading is nothing but trouble. I also wrote much of the documentation about dependencies and Aloha Editor integration (in the Aloha guides and in one other project that depends on Aloha), and I know that such documentation is difficult to understand.

That is not to say that requirejs is bad. Requirejs makes sense if you implement an application and have control over the script loading. But, it makes no sense for a library to force asynchronicity onto users that don't know how to, or don't want to, or simply can't handle it.

Contributor

deliminator commented Oct 4, 2012

@wimleers

The i18n question is still open. The i18n files can be built into the individual module files,

<script src=".../aloha-en.js"></script>
<script src=".../plugins/ui-en.js"></script>
<script src=".../plugins/format-en.js"></script>

Or, the i18n files can be individually loaded:

<script src=".../aloha-i18n-en.js"></script>
<script src=".../aloha.js"></script>
<script src=".../plugins/ui-i18n-en.js"></script>
<script src=".../plugins/ui.js"></script>
<script src=".../plugins/format-i18n-en.js"></script>
<script src=".../plugins/format.js"></script>
Contributor

wimleers commented Oct 5, 2012

The latter makes more sense to me, for two reasons:

  1. You don't get "big JS blobs" for every single language.
  2. Partially thanks to point 1: if a user likes to switch from language A to language B, or because the same computer is used by multiple people who prefer different languages, or even when there's a reverse proxy in between, cachability is better if the language files are completely separate.

Imagine the extreme case: a big multilingual site. They can serve the exact same "base JS" to everyone (and even aggregate it all into one file), they can then tailor (possibly also aggregated) just the "translations JS" to the appropriate people.

Contributor

deliminator commented Oct 5, 2012

Thanks @wimleers

You are right, the latter makes more sense. If the user wants to combine the i18n and code into a single file, he can always concatenate it himself.

Contributor

wimleers commented Oct 5, 2012

Precisely :)

goba commented Oct 5, 2012

Wim asked me to comment here since I'm working with the Drupal JS i18n system. What Drupal does is that we have "markers" in JS files for strings (which are also active logic for looking up translation), such as Drupal.t('String to be translated'); then Drupal includes an extra JS file generated for the page that includes all the translations for all the JS files that were added to the page. So pages using the same set of JS files get the same aggregated translation file, while other pages using other sets of files use another generated JS file for translation.

I also think the later makes more sense, since you have one definite place to go for the logic and only translation files to deal with.

Contributor

wimleers commented Oct 5, 2012

Related issue: #750.

Contributor

deliminator commented Oct 6, 2012

Thanks @goba for the feedback.

Contributor

wimleers commented Oct 10, 2012

BTW, related to this, is the fact that Aloha is currently deciding itself when it should initialize. The only way to prevent that, is by setting the aloha-defer-init="true" on the script tag that references aloha.js. This implies that it is impossible for the CMS/framework/web developer to aggregate (combine/concatenate/bundle, whatever you call it) aloha.js with other JS.

This will definitely make it harder to get Aloha in Drupal core.

Contributor

deliminator commented Oct 10, 2012

The problem with aloha initializing immediately is that you can't configure it after loading it, right?

I suppose we could implement a lazy-init approach, where Aloha initializes its plugins and reads its configuration from the Aloha global as late as possible - for example, when the first call to $(..).aloha() or Aloha.bind() is made. Not sure whether that is the best approach, feedback welcome.

What is the problem with configuring Aloha before including it though? I would expect that other libraries do something similar?

Contributor

wimleers commented Oct 10, 2012

The problem with aloha initializing immediately is that you can't configure it after loading it, right?

Indeed.

Not sure whether that is the best approach, feedback welcome.

That would be an improvement for our use case, but I'm also not sure what the best trade-off is here. I'll ask @theodoreb to comment on this.

What is the problem with configuring Aloha before including it though? I would expect that other libraries do something similar?

Drupal has (and I'm sure other CMSes/frameworks do too) a well-defined system for passing settings from the server (PHP) to the client (JS). All these settings end up in the global Drupal object, or more specifically, in Drupal.settings. We also have a well-defined way to ensure all JS is run and controlled in a consistent manner, for that we have the Drupal.behaviors system. Once the page is ready, each behavior gets the opportunity to attach itself. (And if more HTML is loaded through AJAX, then we'll also apply the behaviors to that.)
Therefor, the only way that makes sense for Drupal to use Aloha Editor, is using the Drupal.settings mechanism to set Aloha.settings. We do that by setting Drupal.settings.aloha.settings on the server, which a Drupal behavior for attaching Aloha to the relevant editables then sets on Aloha's settings: Aloha.settings = Drupal.settings.aloha.settings.

Aloha is — to my knowledge — the first and only library that automatically initializes itself in such a way that is impossible to configure it after the JS has been loaded.

Contributor

theodoreb commented Oct 10, 2012

I think there is an alternate way of defering this, doing Aloha.defer = true; or something that was introduced at the same time as the data attribute. That could help here. Our issue is that we can't guarantee we can add an attribute to a script tag for init. But yeah the ability to reconfigure could be helpful, we might have crazy pop-in scenarios to support.

Contributor

deliminator commented Oct 10, 2012

I think there is an alternate way of defering this, doing Aloha.defer = true; or something that was introduced at the same time as the data attribute.

If you set Aloha.deferInit to true, this property will be set to a function in aloha.js, which can be called to initialize aloha.

Aloha is — to my knowledge — the first and only library that automatically initializes itself in such a way that is impossible to configure it after the JS has been loaded.

The biggest culprit here I think is that most plugins read Aloha.settings in the init method and then set up event handlers and other global state.

It would be great if you could just say Aloha.config({...}) to reconfigure aloha at any time. But this would require us to change every single plugin.

Easiest fix would be just not to initialize plugins until the first call to $(...).aloha() is made.

Member

evo42 commented Oct 10, 2012

It would really be great to use Aloha.config({...}) to reconfigure aloha at any time!

If the config changes after the first $(...).aloha() can we the init all plugins again?

Contributor

deliminator commented Oct 10, 2012

If the config changes after the first $(...).aloha() can we the init all plugins again?

Reinitializing plugins wouldn't work since then event handlers that plugins set up during init would be bound twice and some global state would probably become inconsistent.

Contributor

wimleers commented Oct 10, 2012

I feel like the conversation is derailing? I only wanted to touch upon initialization, and now we're discussing global configuration vs. per-editable configuration…


Aloha.config({…}) :)

Right now, only a few plug-ins support per-editable configurations. E.g. the common/format plugin (see http://aloha-editor.org/guides/plugins.html). Either we need to standardize on that, or we need something like Aloha.config({…}).
Because right now neither is fully supported, we need to do something like the Drupal plug-in, which is dynamically hiding things in the UI in an extremely hacky (and unreliable!) way. That's essentially what http://drupal.org/node/1786550 is about.

Contributor

deliminator commented Oct 10, 2012

Which plugins don't support per-editable configuration? We just have to implement it for those plugins, right?

Something like Aloha.config({...}) can't be a replacement for per-editable configuration, can it? It would re-configure aloha globally, not per-editable.

I looked at the issue you linked to, but I didn't really get what the problem is (I didn't read all of it). Can the issue be solved by lazy initialization?

Member

evo42 commented Oct 10, 2012

Just to mention: quite all plugins support per-editable config -- for some it's just on/off ...

Why should Aloha.config({...}) not be a replacement for per-editable config? Thought it would be possible to pass a configuration like is now...

I would even think jQuery('.selector').aloha({...}) would be a good idea...

Member

evo42 commented Oct 10, 2012

... I didn't mean replacement ...

Contributor

wimleers commented Oct 11, 2012

Well, from a pure-Drupal POV, that would make sense. We explicitly have to call .aloha() already anyway whenever the user starts to edit something. Why?

  1. on the front-end: things that become editable are not editable right away, they only become editable once the user clicks on them.
  2. on the back-end: we need to support toggling between different formats. That is implemented through a custom drupal plug-in. It used to use a data-allowed-tags attribute that would be updated whenever the user switches to another text format, but now it uses class-based configuration as well (.text-format-filtered-html, .text-format-full-html …). However, Aloha Editor does not support this "dynamic editable config changes" thing, so we have to call .mahalo() and .aloha() anyway. (This is fine, btw, but that's why we have to call .aloha() potentially many times.)

But I understand that it wouldn't make sense from your POV, because what you've been doing for a long time now is initialize Aloha once, at the beginning of the page, for all editables, and let Aloha figure out on its own for each editable. For these simpler, less dynamic cases, that also makes a lot of sense.

Contributor

deliminator commented Oct 11, 2012

As far as I can tell, making a call to .mahalo() to destroy an editable and then making a subsequent call to .aloha() doesn't allow to re-configure plugins (there may be some things that you can re-configure this way, but that would be more coincidence than intent).

If I am reading what you say right, then you absolutely do need a way to reconfigure aloha? (Otherwise users would not be able to switch between text formats.)

You could of course emulate the re-configuration by having a distinct editable id for each text format and using the per-editable configuration?

So, to summarize:

  • you need deferInit or lazy initialization (lazy-initialization is not currently implemented but would be an improvement over deferInit since it doesn't require the user to set Aloha.deferInit and make the extra function call)
  • you need re-configurability for all plugins (not currently implemented)
  • you need per-editable configuration for all plugins (already implemented by possibly not 100%)
Contributor

wimleers commented Oct 11, 2012

If I am reading what you say right, then you absolutely do need a way to reconfigure aloha? (Otherwise users would not be able to switch between text formats.)

That's right. Our drupal plug-in binds to aloha-editable-activated and picks up changes that way.

You could of course emulate the re-configuration by having a distinct editable id for each text format and using the per-editable configuration?
That's insufficient, and I tried to explain that above but clearly failed. The problem is that an editable's text format can change on-the-fly! So it's not as simple as you describe it, unfortunately.

per-editable configuration for all plugins (already implemented by possibly not 100%)
WOW, I had no idea! This is implemented by plugin.js/getEditableConfig(), no?
The example I found implement it in a [https://github.com/evo42/Aloha-Editor/commit/035150c7c2f4af817b21a8ac9e4ac42b35a50e6f#L1L148](very fragile manner), and I had wrongly assumed that it would be similar elsewhere.

I'm not sure if we need the ability to reconfigure. If we were able to tell AE "Hey! It looks like the config you're currently using may no longer be the right one because the class of the editable has changed.", that'd be fine too. It'd be a "soft" form of reconfiguring, I guess.

Member

evo42 commented Oct 11, 2012

What you found is a special case and just in the contentHandler plugin -- think it was because contentHandler config is not in the plugin section or can be triggered in other ways (eg on initEditable / paste)

In most other cases it's like here: https://github.com/alohaeditor/Aloha-Editor/blob/dev/src/plugins/common/format/lib/format-plugin.js#L231 (with getEditableConfig() as you noted already)

Contributor

deliminator commented Oct 11, 2012

I'm not sure if we need the ability to reconfigure. If we were able to tell AE "Hey! It looks like the config you're currently using may no longer be the right one because the class of the editable has changed.", that'd be fine too. It'd be a "soft" form of reconfiguring, I guess.

I understand now what you are doing - you are changing the class of the editable, then call .mahalo() and .aloha() to get the new configuration. That's OK for you? Isn't this conversation about changing this?

Contributor

wimleers commented Oct 12, 2012

No, that's actually fine. As I said 2 days ago:

I feel like the conversation is derailing? I only wanted to touch upon initialization, and now we're discussing global configuration vs. per-editable configuration…

The only thing that is really a problem for us, is <script aloha-defer-init="true" src="aloha.js" />, because it prevents aggregation.

Contributor

deliminator commented Oct 12, 2012

OK. I think the lazy initialization is the way to go. I've seen people trying to configure aloha after it has loaded and not figuring out what the problem is (there was a stackoverflow question about that). So, the lazy init approach would be generally a good idea.

Contributor

wimleers commented Oct 12, 2012

It would also solve #752 :)

deliminator was assigned Oct 13, 2012

Contributor

wimleers commented Oct 23, 2012

FWIW, in Drupal, we need to be able to load Aloha dynamically. That means: don't load Aloha during the initial page load, but only after some editable HTML content has been loaded through an AJAX call. aloha-defer-init="true" is making that much harder than necessary.
Now that I think of it, it is actually likely going to be a blocker for Aloha in Drupal core, because http://drupal.org/node/1664602 is very unlikely to to get committed.

(See what I said above: #737 (comment))

Contributor

deliminator commented Oct 23, 2012

I think that lazy initialization is the way to go. @draftkraft please comment.

scribu commented Oct 23, 2012

At the beginning of the thread, someone mentioned document.write(). Is that really necessary? Couldn't dynamically created <script> tags be used instead?

Contributor

deliminator commented Oct 23, 2012

Dynamically created script tags would work as well as far as I can tell. I've seen this being done in the google closure library. Is there a problem with document.write?

Contributor

deliminator commented Oct 23, 2012

Thank you for the link.

I was wrong about google closure library injecting script tags. I looked again and they also do document.write.

I can remember now why this problem can only be solved with document.write. IE (and maybe other browsers) will not allow you to modify the currently read/evaluated element. So, it's not possible to add a script element after the script element that is currently being loaded/executed, except with document.write.

Contributor

wimleers commented Oct 24, 2012

Drupal has an "AJAX Commands Framework". For inserting new JS, it uses the following:

{
command: "insert",
data: "<script src="http://localhost/d8/core/modules/field/modules/text/text.js?mcecvk"></script>",
method: "prepend",
selector: "head",
settings: null
}

I.e. it uses jQuery's .prepend().

Contributor

deliminator commented Oct 24, 2012

I suppose this is for asynchronous script loading?

Contributor

wimleers commented Oct 25, 2012

Correct. Imagine a page /foo has loaded. It then needs to load a form through AJAX; that form needs some additional JS. The above is then to load the additional JS.

Contributor

draftkraft commented Oct 29, 2012

Hi, thank you all for your comments and ideas! Indeed some people complained about the loading mechanism of Aloha Editor being to complicated as it needs to be. It seams that other JS libraries use a more simpler approach. Our intension was to improve the loading and build and make it an implicit functionality of Aloha Editor. But we learned that it makes us less flexible and make the learning curve for newbies higher.

I would completely agree with a refactoring of the loading mechanism as @deliminator proposed. @scribu I agree that document.write is a bad practice but it's the only way make it work in all browsers and it should only be used in development environment. If you want to take Aloha Editor in production you should anyway combine and minimize it to your production needs.

I also think that lazy loading is a good way to go as it fulfils all requested needs and is still backwards compatible.

Contributor

wimleers commented Oct 29, 2012

Please note that document.write() is not at all a necessary evil. See my last but one comment, jQuery.prependTo('head') should work. It works for Drupal core, across all browsers.

Agreed on the lazy loading.

Contributor

deliminator commented Oct 29, 2012

document.write() is necessary to achieve synchronous loading. prependTo('head') only works for asynchronous loading.

Contributor

wimleers commented Oct 30, 2012

I see. Sources? (Articles/blog posts/…)

Contributor

deliminator commented Oct 30, 2012

Source is my own experience. If you try to modify the "head" element, for example from a script element while it is being loaded, you get a javascript error, at least with IE. This means that you can only reliably inject script tags after the document has loaded.

OK, that doesn't mean that you can't load script tags synchronously after the document has loaded. I'm not exactly sure what happens if you inject several script tags after the document has loaded - are the guaranteed to be loaded synchronously?

I don't want to spend time trying out script element injection only to find out that it doesn't work. I think document.write is perfectly alright. The google closure library also does it with document.write by the way.

Contributor

wimleers commented Oct 30, 2012

Fair enough :)

Member

evo42 commented Nov 2, 2012

don't know if it's useful... does anyone know about http://labjs.com/description.php

Contributor

deliminator commented Nov 2, 2012

Not sure how labjs can help. Seems to be about async loading, which we want to avoid.

Contributor

wimleers commented Nov 5, 2012

labjs is irrelevant to this discussion. labjs should be used on the CMS/app level, not on the lib level. That would pretty much leave us in a semantically similar situation, but practically worse.

Contributor

deliminator commented Dec 2, 2012

I have finalized the dev-modular branch.

You can have a look here to see how aloha loading and intialization works
https://github.com/alohaeditor/Aloha-Editor/blob/dev-modular/src/demo/boilerplate/index-modular.html

Then 'en' locale files are built-in by default. To get another locale you will be able to set Aloha.settings.locale (just like before), but you have to make sure then to include the right i18n.js files (commented out in index-modular.html). However, that doesn't work yet because I have an issue with the mygengo import and export scripts and I will have to talk to Rene to fix that.

Lazy initialization doesn't seems such a good idea on second though, so, with the modular build, Aloha.deferInit is always true (you don't have to set it explicitly) and you have to call Aloha.init() once to set everything up.

It doesn't seem such a good idea because I would have to add dummy functions to the global Aloha object that do nothing but call Aloha.init() and then call the proper function. I'm not sure we want that since it's extra code and it's possible that we miss a dummy function that the user expects to be there, and then he wonders why Aloha.xx() is available when he first does $.aloha() but not before.

Is the mandatory Aloha.init() call OK from Drupal perspective?

I will merge the branch back into dev once I resolved the final issue with the mygengo scripts.

scribu commented Dec 2, 2012

Couldn't Aloha.init() be fired automatically on jQuery(window).loaded() or on jQuery(document).ready() ?

Contributor

deliminator commented Dec 2, 2012

Init requires the settings to be set in Aloha.settings or given as an argument, otherwise it could be called immediately (which is what has always been done). The automatic initialization is problematic for Drupal's dependency mechanism, since automatic initialization requires Aloha.settings to be set before the script is even loaded.

scribu commented Dec 2, 2012

Yes, defining Aloha.settings before loading the script is how I currently initialize Aloha 0.20 in my WordPress plugin:

<script>
var Aloha = {}

Aloha.settings = {
  ...
}
</script>

<script src="{...}/aloha.js" data-aloha-plugins="{...}"></script>

but I don't see how this would prevent calling Aloha.init() automatically, unless I misunderstand what Aloha.init() does.

If Aloha.settings is the only thing that Aloha.init() needs, you could call it immediately in aloha.js:

Aloha.init = function() {

}

...

// jQuery(document).ready(function() {
  Aloha.init();
// });

NB: I don't have much knowledge of Aloha internals; just brainstorming.

scribu commented Dec 2, 2012

Oh, I think I misunderstood what you meant by "problematic for Drupal's dependency mechanism". You mean Drupal can't set Aloha.settings before loading the script?

scribu commented Dec 2, 2012

And even if it couldn't, Aloha.settings could still be set in between:

  1. <script src="{...}/aloha.js"></script>
  2. <script src="{...}/aloha.js">Aloha.settings = ...</script>
  3. DOMReady event fires Aloha.init()

scribu commented Dec 2, 2012

If Aloha.init() must be called manually, I think we should deprecate the practice of defining Aloha.settings directly.

(I'll stop now)

Contributor

deliminator commented Dec 3, 2012

Thank you @scribu for your comments.

The approach in the comment before last would probably work. I think however, both ways, requiring Aloha.settings to be set before the script is loaded, as well as requiring Aloha.settings to be set before the dom is ready, are more difficult to understand than just nothing happening until the init function is called.

I've answered several issues because of the requirement that Aloha.settings is set before the script is loaded. I can understand why people have problems with this. If you have a place in your document where you load your scripts elements, and another place where you have your code, and this approach has always worked for you, you wouldn't expect a library to come along that suddenly makes you mix script loading and code.

If this one place with code is wrapped in a $(function () { ... }), then the same problem would exist for the dom ready event.

evo42 closed this Oct 17, 2013

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment