Include javascript in plugin app

Include javascript in plugin app

by Mike Churchward -
Number of replies: 9
Picture of Core developers Picture of Plugin developers Picture of Testers

In my Moodle activity plugin, I have javascript functions included that are called when selection are made in forms. These functions validate the selections and change the page as appropriate before submitting the form.

How can I do the same thing in the mobile app? Where do I include these functions so the can be called on ionic actions, such as "ionChange"?


Average of ratings: -
In reply to Mike Churchward

Re: Include javascript in plugin app

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

Hi Mike, The bad news is that you can't really reuse existing Javascript, since the app doesn't use AMD/requirejs, and the structure of the DOM is completely different to Moodle web pages. That said, writing Javascript in your plugin and attaching it to an ionic event is perfectly possible. An example looks something like this (from block_news):

  • appjs/newspage.js defines a function called loadMoreMessages (ultimately it's attached to this, I just pass this in to an anonymous function and call it t for neatness, and some level of consistency with AMD code).
  • classes/output/mobile.php returns this javascript file when viewing the news page.
  • templates/mobile_newspage.mustache includes an infinite scroll element, which calls loadMoreMessages in response to its ionInfinite event.

The general syntax looks like (eventName)="handlerFunction($event)" (note, no need to prefix the function name with this., it is assumed). I could equally have a button to load more messages instead of an infinite scroll, with an event binding like (click)="loadMoreMessages($event)"

Average of ratings: Useful (2)
In reply to Mark Johnson

Re: Include javascript in plugin app

by Mike Churchward -
Picture of Core developers Picture of Plugin developers Picture of Testers
Thanks Mark.

That looks like the solution I was looking for. So the 'javascript' argument in the return list of 'news_page' loads the javascript from the specified file into the content displayed in the app, so it can be used within the app html? That makes sense, and is exactly the piece I needed.

Any suggestions as to where I can find details on how to manipulate the DOM in the app? Essentially, I want to be able to select and unselect items like radio buttons.
In reply to Mike Churchward

Re: Include javascript in plugin app

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

Pretty much, yes.

Getting in to the nitty-gritty a bit more, the app uses and angular component to display the page for your plugin, and the 'javascript' string returned is executed in the scope of that component. So if the javascript defines a method or property of this, the method or property is then part of that component, and available to be referenced from the template. If it has contains other arbitrary code, that will be executed within the scope of the component when it is loaded.

In reply to Mark Johnson

Re: Include javascript in plugin app

by Mike Churchward -
Picture of Core developers Picture of Plugin developers Picture of Testers
Okay. So, in the existing web code, I have a function that deselects some options when an other option is selected. The "this" would deal with the one that was selected, but then I need to find the other elements and deselect them. In the web code, I would get all of the elements of that input type, cycle through them looking for known identifiers and names, and then deselect the ones that match.

How would I do that type of location?
In reply to Mike Churchward

Re: Include javascript in plugin app

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

Regarding stuff like radio buttons, a good way to do this is taking advantage of angular's data bindings. If you link a property in your template to a variable in javascript, changes to that variable will automatically be reflected on the page.

For example, if you had the following radio button (these are really angle brackets not curly brackets, but the filters are messing it up): {ion-radio value="foo" [checked]="checkbox1"}{/ion-radio}

And the following javascript for this page:

function(t) {
    t.checkbox1 = false;
    t.toggleCheckbox = function() {
        t.checkbox1 = !this.checkbox1;
    }
}(this);

Then calling the toggleCheckbox() method should change the state of your checkbox. (I found the name of the checked propery by looking at the Ionic component documentation for radio buttons).

In reply to Mark Johnson

Re: Include javascript in plugin app

by Mike Churchward -
Picture of Core developers Picture of Plugin developers Picture of Testers
Thanks. I now know that I know a great deal less about how this works than I thought... 🙁

Starting with understanding how the ionicframework documentation is arranged.

I may need to learn more about how ionic conventions work. For example, you included:
[checked]="checkbox1"
I have used:
checked="true"
But never what you have specified there. I assume the '[]' means something else? Is there a good place to start learning about why things like '[]' mean something else?

In your example, is the [checked]="checkbox1" creating an identifier for the specific radio element tied to the "checked" property?
In reply to Mike Churchward

Re: Include javascript in plugin app

by Mike Churchward -
Picture of Core developers Picture of Plugin developers Picture of Testers

This conversation continued in the developer chat... Here is the transcript:

Dani Palou, [Oct 10, 2019 at 10:51:19 AM]:
[] means that what you're passing as a parameter is a variable or a primitive value. If you use:

checked="checkbox1"

you're passing a string with value "'checkbox1'. Since there are no [], the content is just a string. However, if you do:

[checked]="checkbox1"

Angular is going to check if a variable checkbox1 exists and pass that value.

Mark Johnson, [Oct 10, 2019 at 10:51:46 AM (2019-10-10, 10:52:22 AM)]:
checked="true" means "the 'checked' property is set to the string 'true'". [checked]="checkbox1" means "the 'checked' property is bound to the value of the variable 'checkbox1'"

Dani Palou, [Oct 10, 2019 at 10:52:41 AM (2019-10-10, 10:52:51 AM)]:
in case of booleans, in our components we add some code to handle checked="false" (string 'false') same as [checked]="false" (boolean). But if you're using angular or ionic components, using checked="false" might not work since a string is evaluated as true

Mike Churchward, [Oct 10, 2019 at 10:53:05 AM]:
And the variable "checkbox1" is now part of the element in question?

Dani Palou, [Oct 10, 2019 at 10:54:15 AM]:
you need to assign it to "this", since "this" is the instance of the component that renders the view

it basically looks for variables in the "root" component of the view

Mike Churchward, [Oct 10, 2019 at 10:58:34 AM (2019-10-10, 10:58:49 AM)]:
Right... But something like this:
    <ion-radio value="foo" [checked]="checkbox1"></ion-radio>
means that this particular radio element now has a variable/property named "checkbox1" that is bound to the element's "checked" property?

And that happened because of the "[]" structure?

Mark Johnson, [Oct 10, 2019 at 10:59:14 AM]:
no, it has a property called "checked", "checkbox1" is part of the angular component

or rather, the page's angular component, not the checkbox's

Mike Churchward, [Oct 10, 2019 at 11:00:55 AM]:
>> "checkbox1" is part of the angular component

Meaning that "checkbox1" is a variable of the specific ion-radio that can be used to manipulate the "checked" property of that same specific ion-radio. And exists in javascript.

Mark Johnson, [Oct 10, 2019 at 11:01:39 AM]:
checkbox1 is independant of the ion-radio. Other elements may also have properties bound to checkbox1

Mike Churchward, [Oct 10, 2019 at 11:02:13 AM]:
Okay... So "checkbox1" is a variable of the angular page component, tied to the specific ion-radio element...

No... I guess you just said that's not true.

Mark Johnson, [Oct 10, 2019 at 11:02:58 AM]:
<ion-radio value="foo" [checked]="checkbox1"></ion-radio><ion-radio value="bar" [checked]="checkbox1"></ion-radio> is valid

Mike Churchward, [Oct 10, 2019 at 11:03:06 AM]:
So the page variable "checkbox1" has a one to many relationship to properties of elements on the page?

Dani Palou, [Oct 10, 2019 at 11:03:17 AM]:
yep, you can pass it to many elements

Mike Churchward, [Oct 10, 2019 at 11:03:23 AM]:
Interesting...

Mark Johnson, [Oct 10, 2019 at 11:05:45 AM]:
If you want to expand your brain a bit futher, for [foo]="bar", bar can be any javascript expression, although this isn't necessarily advisable from a performance point of view. 
You could do [foo]="bar > 0" or [foo]="'prefix_' + bar"

Mike Churchward, [Oct 10, 2019 at 11:08:32 AM]:
Mark, using your example from the forum, if I had:
    <ion-radio value="foo" [checked]="checkbox1" ionSelect="toggleCheckbox()"></ion-radio>
    <ion-radio value="bar" [checked]="checkbox1" ionSelect="toggleCheckbox()"></ion-radio>

Would the "toggleCheckbox()" then impact both radio buttons when I click one of them?

Mark Johnson, [Oct 10, 2019 at 11:09:53 AM]:
(ionSelect) but yes

Mike Churchward, [Oct 10, 2019 at 11:10:42 AM]:
So:
<ion-radio value="foo" [checked]="checkbox1" (ionSelect)="toggleCheckbox()"></ion-radio>
    <ion-radio value="bar" [checked]="checkbox1" (ionSelect)="toggleCheckbox()"></ion-radio>

Well now... That IS interesting...
So, where in the angular or ionic documentation can I see things like using [] to tie an angular variable to a ionic property?

Mark Johnson, [Oct 10, 2019 at 11:13:52 AM]:
https://ionicframework.com/docs/v3/api/components/button/Button/ has some examples under "Advanced"

You see this quite a lot in the ionic docs when dealing with inputs: ([ngModel]) -  that is *two way data binding*, where changes to the value on the page (e.g. if you select something in a list) change that variable in javascript, without you having to explicitly trigger an event.  I'm not sure how well that interacts with Moodle plugins?

Mark Johnson, [Oct 10, 2019 at 11:22:46 AM]:
Angular knows that something in the template is bound to "checkbox1", so whenever the value of "checkbox1" is changed, it re-renders the template to reflect the change. I dont know if its clever enough to do individual elements, or of it just does the whole template

Dani Palou, [Oct 10, 2019 at 11:23:50 AM]:
2-way data binding should work fine for plugins. In the end Angular is just dealing with a JS variable, he doesn't know if it comes from a plugin or the app itself

Dani Palou, [Oct 10, 2019 at 11:24:39 AM]:
Angular has a tree of components, and the detection mechanism is supposed to be kind of smart when dealing with the tree. I don't know much about the details of how the change detection mechanism works though

Mike Churchward, [Oct 10, 2019 at 11:26:22 AM]:
Specifically, I'm working with <ion-segment> and <ion-segment-button> using [(ngModel)]="CONTENT_OTHERDATA.<%fieldkey%>". That seems to take care of what segment button is selected.

I'll have to experiment to see what happens when I start messing around with the "checked" property directly.

Dani Palou, [Oct 10, 2019 at 11:26:24 AM]:
The function toggleCheckbox will be fired only once when you change a checkbox. However, that function will change  the value of variable that is then passed to 2 components, so it will affect 2 different elements. This is handled by Angular itself (it has something called digest cycles, and in these cycles it checks if the variables have changed and so)

if you're interested in learning more about this, the "[]" dotation is an Angular Input, and the "()" notation is an Angular Output

basically with [] you send data from outside a component to inside a component. With (), a component sends data from the inside to the outside.

2-way data binding is a mix of both, that's why it combines both of them: [(ngModel)]

<my-component [(ngModel)]="myVar"> means that:

-If you change the value of myVar, the component "my-component" will know it and will receive the new value.
-If the component "my-component" changes the value of the variable, your code will also have the new value.