You can handle clicks in a Turnkey view or page override by binding to data already available in the AngularJS scope, by creating an AngularJS directive for custom behavior, or, when necessary, by using a native onclick handler that retrieves the element scope.
Understand why ng-click does not run your function
ng-click is an AngularJS directive. Its value is an AngularJS expression evaluated against the scope of the element; it is not a call into arbitrary browser-global JavaScript.
In a Turnkey view, the data exposed to AngularJS comes from the ViewModel. For example, an item named data inside an ng-repeat is available to that repeated element's scope.
| Markup | Result | Reason |
|---|---|---|
onclick="CallFunction(Template:Data.SomeValue)"
|
Error or invalid JavaScript | AngularJS interpolation (Template:...) writes values into HTML. It does not create valid JavaScript source for an inline event handler.
|
ng-click="CallFunction(data.SomeValue)"
|
Nothing happens when CallFunction is not on the current AngularJS scope
|
AngularJS looks for CallFunction in its scope. A JavaScript function declared outside that scope is not automatically available to ng-click.
|
For standard ViewModel actions, use the action objects exposed by the ViewModel rather than inventing a browser-side function. For example, a generated action can be executed from repeated markup:
<button ng-repeat="oneaction in root.VM.StateActions() | orderBy:'+SortKey'"
ng-class="oneaction.Class"
ng-click="oneaction.Execute(); hidemenu();"
ng-disabled="!oneaction.Enable"
class="myHorizontalbut">
{{oneaction.Presentation}}
</button>
Recommended approach: create a directive
Create an AngularJS directive when the click behavior is custom JavaScript and needs access to the current scope data. A directive connects an HTML attribute to JavaScript that receives the element, its attributes, and its AngularJS scope.
Add the directive to the Ext_Component script
Add the following code to the .js file of your Ext_Component. Replace yourdirectivename, SomeViewModelColumnName, and SomeViewModelAction with names from your ViewModel.
function SomeFunctionToCallThatNeedContextData(ev, data) {
var x = data.SomeViewModelColumnName;
data.Execute("SomeViewModelAction");
}
function InstallTheDirectiveFor_YOURNAME(streamingAppController) {
streamingAppController.directive('yourdirectivename', ['$document', function ($document) {
return {
link: function (scope, element, attr) {
element[0].addEventListener("click", function (ev) {
SomeFunctionToCallThatNeedContextData(ev, scope.data);
});
}
};
}]);
console.trace("YOURNAME Loaded");
}
InstallTheDirectiveFor_YOURNAME(angular.module(MDrivenAngularAppModule));
In this example, scope.data is the object named data in the surrounding AngularJS scope. This is appropriate when the button is placed in markup such as:
<div ng-repeat="data in root.Items">
<button yourdirectivename>
Run the action for {{data.SomeValue}}
</button>
</div>
When the user clicks a button in the repeated list, the directive passes that row's data object to SomeFunctionToCallThatNeedContextData. The function can read data.SomeViewModelColumnName and execute the ViewModel action shown in the example.
Use the directive in component HTML
After the script is loaded, add the directive attribute to the element that should respond to the click:
<button yourdirectivename>
When you press this, SomeFunctionToCallThatNeedContextData is called
</button>
Keep the directive name and the HTML attribute name identical. The directive must be installed on the AngularJS module before AngularJS processes the component markup.
For component structure and loading scripts with components, see Documentation:Using Google Charts. For a larger directive example that uses repeated ViewModel data, see Documentation:A Trello like Board In MDrivenTurnkey.
Alternative: use native onclick with the element scope
Use a native onclick handler when you are working in an overridden page and need to invoke a function declared in that page's <script>. Obtain the AngularJS scope from the clicked element and pass it to the function.
<button onclick="DelayedSave(angular.element(this).scope())">
Save later
</button>
<script>
function DelayedSave(scope) {
setTimeout(function () {
scope.StreamingViewModelClient.CallServerAction('JournalSeeker', 'LazySave');
}, 2000);
};
</script>
This pattern calls the LazySave action on the JournalSeeker ViewModel after two seconds. A button may have both ng-click and onclick; both handlers are called.
You can also use the element scope to set ViewModel data before posting an action. The following pattern gets the scope, assigns the clicked element ID to a property under root, and performs the action inside $apply:
<script>
function ToggleSeat(theelement) {
var thescope = angular.element(theelement).scope();
thescope.root.jsClickSeat = theelement.id;
thescope.$apply(function () {
thescope.root.VM.Execute_Post('BuyTickets2', 'jsToggleSeat');
});
}
</script>
See Documentation:View/Page override for the scope-access pattern and delayed-action example. See Documentation:MDriven Movie Theatre Part 2 for the click-to-ViewModel-action pattern.
Minimal attribute workaround
If you need a quick bridge to a browser-global JavaScript function, AngularJS can bind a value into a custom HTML attribute and the native handler can read that attribute:
<button p1="{{data.SomeValue}}"
onclick="CallFunction(this.getAttribute('p1'))">
Call function
</button>
Here, p1 is a custom attribute. AngularJS updates it from data.SomeValue, and this.getAttribute('p1') passes the resulting value to CallFunction.
Use this only as a minimal workaround. It transfers a bound attribute value to native JavaScript, but it does not make CallFunction part of the AngularJS scope and does not provide the directive structure needed for reusable custom behavior. Prefer a directive for component behavior, or the scope pattern above for page-override scripts.
Checklist
- Confirm that the value you reference is available in the current ViewModel scope. Root-level values are normally addressed as
root.PropertyName; values insideng-repeatuse that repeat's item name, such asdata.SomeValue. - Do not put
Template:...inside JavaScript arguments in an inlineonclickhandler. - Use
ng-clickfor functions and action objects that exist on the current AngularJS scope. - Create a directive when custom JavaScript must handle an element and its current scope data.
- In an overridden page, pass
angular.element(this).scope()to a native handler when the handler needs access to the ViewModel scope.
