formslib plus ajax

formslib plus ajax

by Howard Miller -
Number of replies: 24
Picture of Core developers Picture of Documentation writers Picture of Particularly helpful Moodlers Picture of Peer reviewers Picture of Plugin developers
Sorry, another one (Moodle 2.0 if it matters)...

Is it possible to create a Moodle (formslib) form that contains ajax. Specifically, the items in the forms second select element depend on the selection made in the first select element?

I couldn't think of any examples quite like this...

Thanks smile
Average of ratings: -
In reply to Howard Miller

Re: formslib plus ajax

by Joseph Rézeau -
Picture of Core developers Picture of Particularly helpful Moodlers Picture of Plugin developers Picture of Testers Picture of Translators

Same question. It seems to me that there would be a number of cases where the items in one given element would depend on selection made in another element. And I'm surprised that we cannnot find examples in current Moodle 2.0 modules & plugins.

Joseph

NB.- I asked for something like that in this post.

In reply to Howard Miller

Re: formslib plus ajax

by Tim Hunt -
Picture of Core developers Picture of Documentation writers Picture of Particularly helpful Moodlers Picture of Peer reviewers Picture of Plugin developers

Not currently possible.

I suspect it was not implemented originally, because Moodle is meant to work with JS off, for accessibility reasons. However, it would be perfectly possible to implement this in a progressive-enhancement way.

However, no one has done so yet, so you are on your own.

What exactly are you trying to do, an how important is it that you use this interface?

In reply to Tim Hunt

Re: formslib plus ajax

by Howard Miller -
Picture of Core developers Picture of Documentation writers Picture of Particularly helpful Moodlers Picture of Peer reviewers Picture of Plugin developers
Hmmm...


It's a kind-of drill down thing. Some select boxes each of which depend on the one before them. For example, selecting a course from the first populates that course's users in the second and then some characteristics of the selected user in the third. It's a report - so the idea is that you can quickly make different selections without going through lots of clunky screens.

The alternative, I suppose, is that the form is submitted on each one being changed. I can't see how you would add a bit of javascript to the control at all. Perhaps formslib simply isn't the answer here.

Having screen after screen with a "Next" button isn't really acceptable in this particular application. It's 2011 after all big grin
In reply to Howard Miller

Re: formslib plus ajax

by Mark Johnson -
Picture of Core developers Picture of Particularly helpful Moodlers Picture of Peer reviewers Picture of Plugin developers

I might have misunderstood the issue here, but is there anything stopping you creating the form with formslib, and attaching the javascript separately?

I've taken this approach before when I wanted a form to autosave when a field was altered - just using formslib to do the legwork of displaying the form and handling it when submitted, then adding javascript to the page which attaches event listeners to the fields.

In reply to Mark Johnson

Re: formslib plus ajax

by Howard Miller -
Picture of Core developers Picture of Documentation writers Picture of Particularly helpful Moodlers Picture of Peer reviewers Picture of Plugin developers
Probably... I had forgotten that I can use the attributes field for each addElement to get the event call in.

I'm a bit useless with javascript which doesn't help either smile
In reply to Howard Miller

Re: formslib plus ajax

by Frank Ralf -
Hi Howard,

Just a quick comment from the byline: Don't insert event handlers directly into the HTML code but add them afterwards using JavaScript. See Development:JavaScript_guidelines#Minimise_inline_JavaScript for details.

Cheers,
Frank
In reply to Frank Ralf

Re: formslib plus ajax

by Howard Miller -
Picture of Core developers Picture of Documentation writers Picture of Particularly helpful Moodlers Picture of Peer reviewers Picture of Plugin developers
Ok... so, how do I add an event to make changing a value in a select control submit its parent form?
In reply to Howard Miller

Re: formslib plus ajax

by Mark Johnson -
Picture of Core developers Picture of Particularly helpful Moodlers Picture of Peer reviewers Picture of Plugin developers

When you put the field's name in addElement, it automatically gives it an ID. Say you called it "select1", the id would be "id_select1". There's an exception to this (I can't remember which element it is) in which case you'll need to define an id in the attributes.

You'll then want to get the select and the form using YUI, (assuming you're using moodle2/YUI3) and attach an event listener:

Y.on('change', submit_form, '#select1');
function submit_form() {    
var form = Y.one('#mform1'); // The id for the moodle form is automatically set.
var form.submit();

If you wanted to be even more fancy, rather than submitting the form, you could do an AJAX request to get the data required for the next select box and update it on the fly.

In reply to Mark Johnson

Re: formslib plus ajax

by Howard Miller -
Picture of Core developers Picture of Documentation writers Picture of Particularly helpful Moodlers Picture of Peer reviewers Picture of Plugin developers
Thanks Mark...

Sorry for being so stupid, but how do I get that into the page. Is that a 'yui' call or a 'requires_js" call or whatever? I'm beyond confused..... sad

Also, where does the 'Y' get defined? Is this documented somewhere (I can't find anything)?

BTW...

In http://phpdocs.moodle.org/HEAD/moodlecore/page_requirements_manager.html

it says in the top bit to use init_js_call which isn't actually in the list of methods!
In reply to Howard Miller

Re: formslib plus ajax

by Mark Johnson -
Picture of Core developers Picture of Particularly helpful Moodlers Picture of Peer reviewers Picture of Plugin developers

That's fine, we all have the learn once smile

The best documentation to read is here: http://docs.moodle.org/en/Development:JavaScript_guidelines

Essentially, you need to define M.your_plugin as an object in a javascript file. You then define the submit_form() function as a method of M.your_plugin, and put the Y.on() call in the init method of the object. You then include the javascript file with $PAGE->requires_js() and call the init method with js_init_call (see docs).

Y is a bit complicated, it took me a while to really understand what happens, but here's a brief explanation:

Y is a namespace generated when YUI is initialised containing the methods from all the loaded modules.  Internally, moodle initialises Y and passes it to the module's init function in js_init_call, as the first parameter. To access Y from your init function, you need to do this:

M.my_plugin.init = function(Y) { //Y is now accessible in the scope of init().

However, you probably want access Y in your object's other method's too, so you need to do something like this:

M.my_plugin.init = function(Y) { //Y is now accessible in the scope of init().
    this.Y = Y; // this.Y is now accessible from all methods of M.my_plugin
}

For tidiness, I always start any other method using Y with "var Y = this.Y" so I don't have to keep prefixing with this.

You may also find this page useful (or more confusing winkhttp://docs.moodle.org/en/Development:How_to_create_a_YUI_3_module

In reply to Mark Johnson

Re: formslib plus ajax

by Howard Miller -
Picture of Core developers Picture of Documentation writers Picture of Particularly helpful Moodlers Picture of Peer reviewers Picture of Plugin developers
Ok I read those docs... some *more* questions...

The M.your_plugin thing. What if it isn't a module? What if it's a report somewhere or a local plugin. What's the syntax for this thing? I guess the 'M' is just an arbitrary "thing"

I can't get my head around the Y thing at all. Can you point me at any complete examples in Moodle 2 where I can see it all in action? The *only* place I can find anything similar is in the quiz module. E.g.


init: function(Y, timeleft) {
M.mod_quiz.timer.Y = Y;
M.mod_quiz.timer.endtime = new Date().getTime() + timeleft*1000;
M.mod_quiz.timer.update();
Y.one('#quiz-timer').setStyle('display', 'block');
},


Although Y seems to get used all over the place, possibly in a different context.

In reply to Howard Miller

Re: formslib plus ajax

by Mark Johnson -
Picture of Core developers Picture of Particularly helpful Moodlers Picture of Peer reviewers Picture of Plugin developers

First of all, dismbiguation of terms:

  • A YUI Module is subset of YUI functionality, contained in it's own namespace, e.g Y.Node
  • A Moodle Module, I'm sure you understand.
  • A Javascript Module in this context, is a subset of javascript functionality contained in it's own M. namespace.

Any Moodle plugin can define it's own Javascript Module, whether it's a Moodle Module or a different type of plugin. Usually this will be in the plugin's module.js file, and be called M.plugintype_pluginame (similar to the lang file naming convention), but the object passed as the 4th parameter of $PAGE->requires->js_init_call() allows you to customise this.

The best example (i.e. the one I'm most familiar with) of Y's usage that I can point you to is my quickfindlist block.

Take a look at the bottom of get_content() in block_quickfindlist.php. This defines $jsmodule (the Javascript module's parameters, including the name, the file it's stored in, and the YUI modules it needs), $jsdata (extra parameters to pass with the init call, in addition to Y), and then passes them, along with the name of the module's init function, to js_init_call(). Note that as it's a block, it uses $this->page in place of $PAGE.

Now look at module.js. It defines M.block_quickfindlist (which is the module we specified with $jsmodule) and the init() method (which we referenced in js_init_call()).  init accepts 6 parameters: Y, and the 5 set in $jsdata.  It then stores a copy of Y in M.block_quickfindlist.Y so it can be used in other methods, and uses Y itself to call Y.on(), adding event listeners to the block.
M.block_quickfindlist.search() is defined futher down module.js - it needs acces to Y too, so we create a copy in the method's scope for easy referencing, before using it to make an AJAX request with Y.io().  You can also see use of M.cfg, another Javascript Module, to access the value of wwwroot.

An important thing to note about Y being used all over the place is that as I understand it, Y is only initialised once with all the YUI Modules required by any Javascript Modules on the current page (hence the requirements manager). It's then passed to each module's init function, so they can use it in their own scope.

The actual "core" javascript code to do this would look something like:

YUI.use('required', 'yui', 'modules', function(Y) { 
// YUI.use initialises Y with required modules,
// and passes it to an anonymous function.
// Y is then available to pass to our own init functions
    M.block_blockname.init_function(Y);
M.report_reportname.init_function(Y, other, parameters);

});

Hope that's answered at least some of your questions smile

In reply to Mark Johnson

Re: formslib plus ajax

by Howard Miller -
Picture of Core developers Picture of Documentation writers Picture of Particularly helpful Moodlers Picture of Peer reviewers Picture of Plugin developers
This is great stuff Mark... we should copy-and-paste into a docs page smile

I'm going to take the liberty of asking one more question before I go and play. How does Y relate to the yui docs. To step back, how do I work out what methods etc. Y has that I can use.

EDIT:
Actually, I'm not sure that is a sensible question. Is this where I have to do something special to load certain YUI modules. I guess the same still applies as to how it then relates to the YUI docs.

EDIT EDIT:
The docs say that you may use the 4th param to add 'other' YUI modules. What it doesn't say is other than *which*. Are there (mysterious) YUI modules loaded as a default?
In reply to Howard Miller

Re: formslib plus ajax

by Mark Johnson -
Picture of Core developers Picture of Particularly helpful Moodlers Picture of Peer reviewers Picture of Plugin developers

Complicated one smile

When you request a module for inclusion, it's classes will be attached to the Y namespace. You can see it's classes by going to the YUI documentation for that module, and looking at the API docs. Each module's classes then has a set of properties and methods.

For example the IO module contains the io class. So telling Moodle that your javascript module requires io will give you access to Y.io and all of its methods.
In general, reading the YUI module's documentation and it's examples will give you a good general idea of how to use it's functionality.

It's also worth noting that some modules will create a shortcut method attached directly to Y for convenience. For instance Y.io.io() can also be called using Y.io(), and Y.Node.one() can be called using Y.one().

I'm guessing that some YUI modules are loaded by default ('base' and 'node' probably are), but the requirements manager should make sure that each one's only loaded once. I always define all the YUI Modules my Javascript Module needs, just to make sure.

P.S. a docs page sounds like a plan, I'll knock something together.

In reply to Mark Johnson

Re: formslib plus ajax

by Howard Miller -
Picture of Core developers Picture of Documentation writers Picture of Particularly helpful Moodlers Picture of Peer reviewers Picture of Plugin developers
Thanks Mark,

That would be fabulous. Without some docs for this there is no chance whatever that anybody is going to use this stuff.

Howard
In reply to Mark Johnson

Re: formslib plus ajax

by Howard Miller -
Picture of Core developers Picture of Documentation writers Picture of Particularly helpful Moodlers Picture of Peer reviewers Picture of Plugin developers
One more question (!!)...

In the 'full' form of the call to the js_init_call you can specify 'strings' which are translated. The only thing it doesn't say is why and where these strings end up. Do you know the n I can add it to the javascript guidelines page?
In reply to Howard Miller

Re: formslib plus ajax

by Mark Johnson -
Picture of Core developers Picture of Particularly helpful Moodlers Picture of Peer reviewers Picture of Plugin developers

Yep,

They end up defined somewhere in the M. namespace, and are accessible from M.util.get_string(), which works exactly like get_string in php.  Without it you'd have to mix PHP and javascript to make strings used by javascript localisable, which isn't nice. I'll add it to the page.

I've added a note to the guidelines page.

In reply to Howard Miller

Re: formslib plus ajax

by Howard Miller -
Picture of Core developers Picture of Documentation writers Picture of Particularly helpful Moodlers Picture of Peer reviewers Picture of Plugin developers
....another quick one smile smile

What does this mean/do...


M.mod_quiz = M.mod_quiz || {};


just javascript ignorance this time smile

EDIT:
I realised what it does (ensures M.mod_quiz exists basically). As this was the first line in module.js does that imply that M.mod_quiz may (or may not) have been previously created?
In reply to Howard Miller

Re: formslib plus ajax

by Mark Johnson -
Picture of Core developers Picture of Particularly helpful Moodlers Picture of Peer reviewers Picture of Plugin developers

Basically, yes. It initialises M.mod_quiz (as an empty object) unless it already exists. I don't think there's any way it should be initialised twice, but it's probably there as a failsafe to stop the existing instance being overwritten.

There's definitely a docs page somewhere that describes a few ways of defining a javascript module (including this syntax) but I'm unable to track it down.

In reply to Howard Miller

Re: formslib plus ajax

by Tim Hunt -
Picture of Core developers Picture of Documentation writers Picture of Particularly helpful Moodlers Picture of Peer reviewers Picture of Plugin developers

Mark gave a perfectly good answer, but here is a different answer that may also help:

The equivalent PHP code is

if (empty($M->mod_quiz)) {

$M->mod_quiz = new stdClass();

}

Average of ratings: Useful (1)
In reply to Tim Hunt

Re: formslib plus ajax

by Howard Miller -
Picture of Core developers Picture of Documentation writers Picture of Particularly helpful Moodlers Picture of Peer reviewers Picture of Plugin developers
A further thought... I noticed that the useful little function 'popup_form" which is ideal fot this sort of thing, has been dumped in the deprecated lib. Looking at the code that adds course resources and activities, it seems to have been replaced (if not in the general sense) by code that looks like...


$select = new url_select($resources, '', array(''=>$straddresource), "ressection$section");
$select->set_help_icon('resources');
$output .= $OUTPUT->render($select);


Which has a touch of the arcane to it. More digging and reading. I'm concerned that all this is just going to lead to everybody filling plugins with straight HTML because all of this quite obscure (and hard to find).

My Moodle 2 productivity is not very good at the moment smile
In reply to Howard Miller

Re: formslib plus ajax

by Martin Contreras -

Hi ,

I´m making some changes in configurable reports. I´d like to use two selects, one depends of the other one. The first one contains the activity module and the other one (child) contains the items in the course of this activity. For example, The first select contains: forum, quiz, lessons, etc. And the next one depends of the activity. I select forum the select contains forum 1, forum 2, forum n. 

 

this is my code:

$options = array('1'=>"Forum", '2'=>'Lesson', '3'=>'Quiz');
$mform->addElement('select', 'opcionesActividad', 'Actividad',$options);

$mform->addElement('select', 'itemsActividad', 'itemsAcividad',$options that depend of the other select);

I hope you can help 

Thanks in advance

Martin