How init a controller variable from the view? (Angular JS) - javascript

I'm working with a custom label witch controller contains a max value, this is for a horizontal bar that reperesents a percentage.
Since the max value for the horizontal bar it's defined dynamically I need to set it directly on the view, so when the ng-repeat starts, it set the caorrect max value.
Currently i have it like this (the view):
<div class="scroll">
<table class="tbody">
<tr ng-repeat="specialItem in specialItems">
<td class="tcellsmall alg_justify" title="{{specialItem.label | translate}}">
<label class='{{specialItem.class}}' ng-click="openLightbox(specialItem)">{{specialItem.label | translate}}</label>
</td>
<td class="tcell" ng-repeat="area in areas">
<span ng-if="specialItem[area] > 0">{{specialItem[area]}}</span>
<horizontal-bar type="{{item}}" value="{{specialItem[area]}}" ng-init="hValues.maxValue = specialItem.itemTotal " ng-if="specialItem[area] > 0" ng-cloak ng-click="clickMeter(area, specialItem.type, specialItem.custom)" area="{{area}}"/>
</td>
<td class="tcellsmall alg_right" ng-click="clickMeter('Plant', specialItem.type, specialItem.custom)">
<label class="cur_pointer">{{specialItem.itemTotal}}</label>
</td>
</tr>
</table>
</div>
And my controller is:
app.directive('horizontalBar', function () {
return {
restrict: 'E',
scope: {
type: '#',
value: '#',
barColor: '#',
threshold: '#', // only used by the Restriktionsverfolgung page
area: '#'
},
templateUrl: 'views/tools/horizontalBar/horizontalBar.html',
replace: true,
controller: ['$scope', '$rootScope', function ($scope, $rootScope) {
$rootScope.hValues = {};
var min = 0;
$rootScope.hValues.maxValue = 0;
var max = $rootScope.hValues.maxValue;
// var max = 300;
var ranges = new AssemblyLive.Models.RangeCollection();
// Init component
$scope.valuePercent = 0;
if ($scope.value !== undefined) {
$scope.$watchCollection('value', function (newValue) {
$scope.SetValue($scope.value);
});
}
// If data is found in the Config file, load it
if (Config.Areas[$scope.type] !== undefined) {
min = Config.Areas[$scope.type].minimum;
max = Config.Areas[$scope.type].maximum;
if (Config.Areas[$scope.type].ranges !== undefined) {
for (var u in Config.Areas[$scope.type].ranges)
ranges.Add(new AssemblyLive.Models.Range(parseFloat(Config.Areas[$scope.type].ranges[u].from), parseFloat(Config.Areas[$scope.type].ranges[u].to), Config.Areas[$scope.type].ranges[u].style));
}
}
//Functions
$scope.SetColor = function (color) {
$scope.backgroundColor = color;
};
$scope.SetValue = function (value) {
value = Math.round(value);
if (value <= min) value = min;
if (value >= max) value = max;
$scope.valuePercent = value / max * 100;
$scope.color = ranges.getValue($scope.valuePercent);
// Only for the Restriktionsverfolgung page
if ($scope.threshold !== undefined) {
if ($scope.valuePercent >= Number($scope.threshold))
$scope.color = "div_error";
else if ($scope.valuePercent >= Number($scope.threshold - 2))
$scope.color = "div_warning";
else
$scope.color = "div_success";
} else if (Utils.isEmpty($scope.color)) {
if (!Utils.isEmpty($scope.barColor)) {
$scope.color = $scope.barColor;
} else {
$scope.color = "grayfill";
}
} else {
$scope.color = $scope.barColor;
}
};
// Meter was clicked
$scope.clickMeter = function () {
if ($scope.type !== '') {
if (Config.Areas[$scope.type] !== undefined && Config.Areas[$scope.type].onClickURL !== undefined) {
window.location.href = Config.Areas[$scope.type].onClickURL;
}
}
};
}],
controllerAs: 'hmCtrl',
link: function ($scope, element, attrs) {
}
};
});
As you can see in the code, I have my $rootScope variables ready for set the value on the view, but it's not working.
Do you have any idea why?
----update----
Using a plugin for check the scopes in firefox, i can see that the max value is been set correctly.
Thanks

Using a plugin for check the scopes in firefox, i can see that the max value is been set correctly.

Related

AngularJS call directive function from the controller on data load

I have a directive function scope.automaticallySelectClosingTime(). It takes the value of first selected dropdown value and creates a list of time on the second select drop-down. It triggers on ng-change.
Directive:
.directive('closingTimeSync', function() {
return {
template: `<md-select ng-disabled="time.uiStoreOpen === false" ng-model="openCloseSet[1]">
<md-option ng-repeat="uiTime in closingTimes" ng-value="uiTime.msValue">
{{::uiTime.display}}
</md-option>
</md-select>`,
replace: true,
transclude: true,
link: function(scope) {
scope.automaticallySelectClosingTime = function(msValue) {
scope.closingTimes = scope.uiAvailableTimes;
var dayMS = 86400000 - 1;
var remainingTimings = [];
var index = scope.closingTimes.map(function(obj) {
return obj.msValue;
}).indexOf(msValue);
index = (index === scope.uiAvailableTimes.length - 1) ? 1 : index + 1;
scope.closingTimes = scope.closingTimes.slice(index, scope.uiAvailableTimes.length);
if (msValue !== dayMS) {
remainingTimings = scope.uiAvailableTimes.slice(1, index - 1);
}
scope.closingTimes = scope.closingTimes.concat(remainingTimings);
};
}
};
})
Controller .
.controller('TestCtrl', function($scope) {
init();
// CREATE AVAIABLE TIMES
$scope.uiAvailableTimes = [];
$scope.uiAvailableTimes.push({
'msValue': 0,
'display': '12:00 Morning'
});
for (var msValue = 900000; msValue <= 85500000; msValue += 900000) { // 90.000ms = 15 min, 85.500.000ms = 11:45PM
$scope.uiAvailableTimes.push({
'msValue': msValue,
'display': moment(msValue).utc().format("h:mm A")
})
}
var dayMS = 86400000 - 1;
$scope.uiAvailableTimes.push({
'msValue': dayMS,
'display': '12:00 Midnight'
});
$scope.closingTimes = $scope.uiAvailableTimes;
function init() {
$scope.uiHoursOfOperation = [] // FROM SERVER
}
});
This works fine. But I've data that coming from the server as well. That means my select fields are preselected via ng-model.
How can I call the $scope.automaticallySelectClosingTime() from the controller. Maybe inside init(). So that it also creates the list of time to second drop-down on init() function call or on page load. And I don't have to create $scope.uiAvailableTimes in the controller.
Working Example: PLUNKER
try to add scope parameter to the directive, you can use this:
.directive('closingTimeSync', function() {
return {
template: `<md-select ng-model="ngModel" ng-disabled="time.uiStoreOpen === false" ng-model="openCloseSet[1]">
<md-option ng-repeat="uiTime in closingTimes" ng-value="uiTime.msValue">
{{::uiTime.display}}
</md-option>
</md-select>`,
scope: {
ngModel: '='
},
replace: true,
transclude: true,
link: function(scope) {
scope.automaticallySelectClosingTime = function(msValue) {
scope.closingTimes = scope.uiAvailableTimes;
var dayMS = 86400000 - 1;
var remainingTimings = [];
var index = scope.closingTimes.map(function(obj) {
return obj.msValue;
}).indexOf(msValue);
index = (index === scope.uiAvailableTimes.length - 1) ? 1 : index + 1;
scope.closingTimes = scope.closingTimes.slice(index, scope.uiAvailableTimes.length);
if (msValue !== dayMS) {
remainingTimings = scope.uiAvailableTimes.slice(1, index - 1);
}
scope.closingTimes = scope.closingTimes.concat(remainingTimings);
};
}
};
})
and also you will need to add the ng-model inside the directive:
<closing-time-sync ng-model="paramFromController"></closing-time-sync>
hope that will resolve your issue.

AngularJS - Show Custom Error Message using Directive

Directive to validate IP:PORT
myApp.directive("validateServerAddress", ['input', function (input) {
var linker = function (scope, element, attrs) {
alert('Tese');
var parts = input.split(":");
var ip = parts[0].split(".");
var port = parts[1];
return validateNum(port, 1, 65535) &&
ip.length == 4 &&
ip.every(function (segment) {
return validateNum(segment, 0, 255);
});
scope.validateNum = function(input, min, max) {
var num = +input;
return num >= min && num <= max && input === num.toString();
}
};
return {
restrict: "EA",
link: linker
};
}]);
HTML Input
<div class="input-group">
<input type="text" validate-serveraddress placeholder="255.255.255.255:5000" name="serveraddress" id="serveraddress" class="color-tooltip form-control" ng-model="serveraddress" required>
</div>
Until user types a valid IP:PORT address, I need to show custom error message 'Invalid Server Address'. The directive works well. Below could be used and custom class can be applied, but I need to show the message just like bootstrap shows for required field.
document.querySelector("input").oninput = function (e) {
this.className = validateIpAndPort(this.value) ? "" : "invalid";
}
.invalid {
color: red;
}
How can I show custom validation using ngMessages via directive as shown in below image ?
Quick directive to validate ip:port I created for you
app.directive("validateAddressPort", function() {
function validateIpAndPort(input) {
var parts = input.split(":");
var ip = parts[0].split(".");
var port = parts[1];
return validateNum(port, 1, 65535) &&
ip.length == 4 &&
ip.every(function (segment) {
return validateNum(segment, 0, 255);
});
}
function validateNum(input, min, max) {
var num = +input;
return num >= min && num <= max && input === num.toString();
}
return {
restrict: "A",
require: "ngModel",
link: function(scope, element, attributes, ngModel) {
ngModel.$validators.invalidaddressport = function(input) {
if(!input) {
return false;
}
return validateIpAndPort(input);
}
}
};
});
html:
<form name="myForm">
<div class="input-group">
<input type="text" ng-model="serveraddress" validate-address-port placeholder="255.255.255.255:5000" name="serveraddress" id="serveraddress" class="color-tooltip form-control" ng-model="serveraddress" required>
</div>
</form>
and now, you can use ngMessages like:
<div ng-messages="myForm.serveraddress.$error" style="color:red" role="alert">
<div ng-message="required">You did not enter a field</div>
<div ng-message="invalidaddressport">Invalid Address:port</div>
</div>
my plunkr: https://plnkr.co/edit/KI9jAqPRkLTYm5EvBKza?p=preview

Angular custom directive not setting value after promise resolve

I have a custom directive, it works great when user is entering value, the problem is when loading the form, the input field is not being rendered.
Here is my directive:
var cuitModule = angular.module('cuitModule', []).directive('cuitDirective', ['$filter', function ($filter) {
return {
require: '?ngModel',
link: link,
restrict: 'E',
scope: {
cuitPlaceholder: '=placeholder'
},
templateUrl: 'js/common/directives/cuit/cuit.directive.html'
};
/*
Intended use:
<cuit-directive placeholder='prompt' model='someModel.cuit'></cuit-directive>
Where:
someModel.cuit: {String} value which to bind only the numeric characters [0-9] entered
ie, if user enters 20-33452648-9, value of 20334526489 will be bound to model
prompt: {String} text to keep in placeholder when no numeric input entered
*/
function link(scope, element, attributes, ngModel) {
// scope.inputValue is the value of input element used in template
scope.inputValue = ngModel.$viewValue;
scope.$watch('inputValue', function (value, oldValue) {
value = String(value);
var number = value.replace(/[^0-9]+/g, '');
// scope.cuitModel = number;
scope.inputValue = $filter('cuit')(number);
var valid = validarCuit(number);
ngModel.$setValidity('required', valid);
if (valid) {
ngModel.$setViewValue(number);
}
});
//source https://es.wikipedia.org/wiki/Clave_%C3%9Anica_de_Identificaci%C3%B3n_Tributaria#C.C3.B3digo_Javascript
function validarCuit(cuit) {
if (cuit.length !== 11) {
return false;
}
var acumulado = 0;
var digitos = cuit.split('');
var digito = digitos.pop();
for (var i = 0; i < digitos.length; i++) {
acumulado += digitos[9 - i] * (2 + (i % 6));
}
var verif = 11 - (acumulado % 11);
if (verif == 11) {
verif = 0;
}
return digito == verif;
}
}}]).filter('cuit', function () {
/*
Format cuit as: xx-xxxxxxxx-x
or as close as possible if cuit length is not 10
*/
return function (number) {
/*
#param {Number | String} number - Number that will be formatted as cuit number
Returns formatted number: ##-########-#
if number.length < 2: ##
if number.length < 10: ##-########
if number.length === 11: ##-########-#
*/
if (!number) {
return '';
}
number = String(number);
// Will return formattedNumber.
// If phonenumber isn't longer than an area code, just show number
var formattedNumber = number;
//Type 20, 23, 24 y 27 Personas FĂ­sicas or 30, 33 y 34 Empresas
var type = number.substring(0, 2);
var main = number.substring(2, 10);
var verifyNumber = number.substring(10, 11);
if (main) {
formattedNumber = (type + '-' + main);
}
if (verifyNumber) {
formattedNumber += ('-' + verifyNumber);
}
return formattedNumber;
};});
This is the html:
<cuit-directive placeholder="'CUIT'" ng-model='vm.merchant.idNumber' required></cuit-directive>
I am invoking it within a form of course.
I am getting the data to my controller through a rest service, so I am doing something like:
function EditMerchantCtrl($state, $ionicHistory, merchantsService, $ionicPopup, $timeout, $ionicLoading) {
var vm = this;
function init(){
merchantsService.get().then(
function(response){
vm.merchant = response.data;
});
}}
I don't know why I can't get that field populated after receiving the response from the service. Any help would be much appreciated.
You should implement the $render function of the ngModelController, try something like this:
ngModel.$render = function() {
scope.inputValue = ngModel.$viewValue;
}
Hope it helps.

Pass scope and parameters to directive - AngularJS

I'm trying to set a class in a ng-repeat with a directive.
I need to pass a parameter to this directive: wineQty
if I use scope: { wineQty: '=' } this works however $scope.bullet1Color is undefined in my html and thus doesn't affect the class that I want.
If I use scope: '#' I get the correct class however I can't specify wineQty
Is there a way to combine theses two syntaxes? so that I can access the scope and pass a paramater?
I've tried { wineQty: '#' } but with no luck sadly.
Here's my directive
angular.module('myApp').directive('wineQtyBullets', function () {
return {
restrict: 'A',
scope: { wineQty: '=', totalBullets: '=', numRedBullets: '=', numOrangeBullets: '#', bullet1Color: '#' },
link: function (scope, element, attrs) {
// defaults
var defaultNumRedBullets = 1;
var defaultNumOrangeBullets = 2;
var defaultTotalBullets = 12;
var defaultWineQty = 0;
// set values from attributes
var numRedBullets = scope.numRedBullets ? scope.numRedBullets : defaultNumRedBullets;
var numOrangeBullets = scope.numOrangeBullets ? scope.numOrangeBullets : defaultNumOrangeBullets;
var totalBullets = scope.totalBullets ? scope.totalBullets : defaultTotalBullets;
var wineQty = scope.wineQty ? scope.wineQty : defaultWineQty;
for (var currentBullet = 1; currentBullet <= totalBullets; currentBullet++) {
var bulletColorClass = 'bg-grey';
if (currentBullet <= wineQty) {
if (currentBullet <= numRedBullets) {
bulletColorClass = 'bg-red';
}
else if (currentBullet <= (numOrangeBullets + numRedBullets)) {
bulletColorClass = 'bg-orange';
}
else {
bulletColorClass = 'bg-green';
}
}
scope["bullet" + currentBullet + "Color"] = bulletColorClass;
}
console.log(scope.bullet1Color);
}
};
}
);
Here's my html
<div class="bullets pull-left ml15 mt6" wine-qty="wine.owned_qty" wine-qty-bullets>
<ul>
<li class="bullet"
ng-class="bullet1Color"></li>
</ul>
</div>
I managed to solve the problem, by changing scope to true and accessing the parameters through attrs
If this can help anyone here's the directive:
angular.module('myApp').directive('wineQtyBullets', function () {
return {
restrict: 'A',
scope: true,
link: function (scope, element, attrs) {
var options = { };
angular.forEach([
'numRedBullets',
'numOrangeBullets',
'totalBullets',
'wineQty'
], function (key) {
if (angular.isDefined(attrs[key]))
options[key] = attrs[key];
});
// defaults
var defaultNumRedBullets = 1;
var defaultNumOrangeBullets = 1;
var defaultTotalBullets = 12;
var defaultWineQty = 0;
// set values from attributes
var numRedBullets = parseInt(options.numRedBullets) ? parseInt(options.numRedBullets) : defaultNumRedBullets;
var numOrangeBullets = parseInt(options.numOrangeBullets) ? parseInt(options.numOrangeBullets) : defaultNumOrangeBullets;
var totalBullets = parseInt(options.totalBullets) ? parseInt(options.totalBullets) : defaultTotalBullets;
var wineQty = parseInt(options.wineQty) ? parseInt(options.wineQty) : defaultWineQty;
for (var currentBullet = 1; currentBullet <= totalBullets; currentBullet++) {
var bulletColorClass = 'bg-grey';
if (currentBullet <= wineQty) {
if (wineQty <= numRedBullets) {
bulletColorClass = 'bg-red';
}
else if (wineQty <= (numOrangeBullets + numRedBullets)) {
bulletColorClass = 'bg-orange';
}
else {
bulletColorClass = 'bg-green';
}
}
scope["bullet" + currentBullet + "Color"] = bulletColorClass;
}
}
};
});
Using "=" means 2-way data binding, and it's definitely fine.
The problem might be that your link function are executed only once at the very beginning, when it's possible that the values of your attributes are not yet assigned (may be caused by some AJAX calls).
I would suggest that you wrap all your link function into a scope.$watch function. Like:
link: function (scope, element, attrs) {
scope.$watch(function() {
return {
wineQty: scope.wineQty,
totalBullets: scope.totalBullets,
numRedBullets: scope.numRedBullets,
numOrangeBullets: scope.numOrangeBullets,
bullet1Color: scope.bullet1Color
}
}, function() {
// Your original code here.
})
}
Then your final result will automatically update if your directive got new attribute values.

AngularJS - Blur + Changed?

What is the easiest way to combine ng-changed and ng-blur?
I've found this post: How to let ng-model not update immediately?
However, this does no longer work in angluar 1.2+
Is there any way to achieve the same behavior?
I guess I have to store a copy of the old value myself and compare the new value to that on blur if I try to do the same, or is there any easier way ?
Use ng-model options.
Like this, ng-change will only trigger when the input is blurred.
<input type="text"
ng-model="a.b"
ng-model-options="{updateOn: 'blur'}"
ng-change="onchange()"/>
This does what I want.
It stores the value on focus, and compares it to the new value on blur, if changed, it triggers the expression in the attribute.
app.directive('changeOnBlur', function() {
return {
restrict: 'A',
require: 'ngModel',
link: function(scope, elm, attrs, ngModelCtrl) {
if (attrs.type === 'radio' || attrs.type === 'checkbox')
return;
var expressionToCall = attrs.changeOnBlur;
var oldValue = null;
elm.bind('focus',function() {
scope.$apply(function() {
oldValue = elm.val();
console.log(oldValue);
});
})
elm.bind('blur', function() {
scope.$apply(function() {
var newValue = elm.val();
console.log(newValue);
if (newValue !== oldValue){
scope.$eval(expressionToCall);
}
//alert('changed ' + oldValue);
});
});
}
};
});
usage:
<input ng-model="foo" change-on-blur="someFunc()" />
How about this solution. Works for me:
<input ng-init="oldValue = value" ng-model="value"
ng-blur="oldValue != value && callYourFunc(foo)">
I am using AngularJs 1.2.x and stumbled upon the ng-change issue of firing on each change. ng-blur can be used but it fires even though there is no change in the value. So both cannot be used efficiently.
With Angularjs 1.3.x, things are easier using ng-model-options like below
to invoke change function "onBlur"
ng-change="ctrl.onchange()" ng-model-options="{updateOn: 'blur'}"
And
to delay invocation of change function by 500ms
ng-change="ctrl.onchange()" ng-model-options='{ debounce: 500 }'"
Now coming to back to the question of getting such things with AngularJs 1.2.x
to invoke change function "onBlur"
html
<input type="text" ng-model="ctrl.a.c" sd-change-on-blur="ctrl.onchange()" />
or
<input type="text" ng-model="ctrl.a.c" sd-change-on-blur="ctrl.onchange(ctrl.a.c)" />
JS
app.directive('sdChangeOnBlur', function() {
return {
restrict: 'A',
scope: {
sdChangeOnBlur: '&'
},
link: function(scope, elm, attrs) {
if (attrs.type === 'radio' || attrs.type === 'checkbox')
return;
var parameters = getParameters(attrs.sdChangeOnBlur);
var oldValue = null;
elm.bind('focus', function() {
scope.$apply(function() {
oldValue = elm.val();
});
})
elm.bind('blur', function() {
scope.$apply(function() {
if (elm.val() != oldValue) {
var params = {};
if (parameters && parameters.length > 0) {
for (var n = 0; n < parameters.length; n++) {
params[parameters[n]] = scope.$parent.$eval(parameters[n]);
}
} else {
params = null;
}
if (params == null) {
scope.sdChangeOnBlur();
} else {
scope.sdChangeOnBlur(params)
}
}
});
});
}
};
});
function getParameters(functionStr) {
var paramStr = functionStr.slice(functionStr.indexOf('(') + 1, functionStr.indexOf(')'));
var params;
if (paramStr) {
params = paramStr.split(",");
}
var paramsT = [];
for (var n = 0; params && n < params.length; n++) {
paramsT.push(params[n].trim());
}
return paramsT;
}
to delay invocation of change function by 500ms
html
<input type="text" ng-model="name" sd-change="onChange(name)" sd-change-delay="300"/>
OR
<input type="text" ng-model="name" sd-change="onChange()" sd-change-delay="300"/>
JS
app.directive('sdChange', ['$timeout',
function($timeout) {
return {
restrict: 'A',
scope: {
sdChange: '&',
sdChangeDelay: '#' //optional
},
link: function(scope, elm, attr) {
if (attr.type === 'radio' || attr.type === 'checkbox') {
return;
}
if (!scope.sdChangeDelay) {
scope.sdChangeDelay = 500; //defauld delay
}
var parameters = getParameters(attr.sdChange);
var delayTimer;
elm.bind('keydown keypress', function() {
if (delayTimer !== null) {
$timeout.cancel(delayTimer);
}
delayTimer = $timeout(function() {
var params = {};
if (parameters && parameters.length > 0) {
for (var n = 0; n < parameters.length; n++) {
params[parameters[n]] = scope.$parent.$eval(parameters[n]);
}
} else {
params = null;
}
if (params == null) {
scope.sdChange();
} else {
scope.sdChange(params)
}
delayTimer = null;
}, scope.sdChangeDelay);
scope.$on(
"$destroy",
function(event) {
$timeout.cancel(delayTimer);
console.log("Destroyed");
}
);
});
}
};
}
]);
function getParameters(functionStr) {
var paramStr = functionStr.slice(functionStr.indexOf('(') + 1, functionStr.indexOf(')'));
var params;
if (paramStr) {
params = paramStr.split(",");
}
var paramsT = [];
for (var n = 0; params && n < params.length; n++) {
paramsT.push(params[n].trim());
}
return paramsT;
}
plnkrs for both approaches are
http://plnkr.co/edit/r5t0KwMtNeOhgnaidKhS?p=preview
http://plnkr.co/edit/9PGbYGCDCtB52G8bJkjx?p=info
how about this plunkr?
using angular's built in ng-blur, update your "persisted value" on blur
<input type="text" ng-model="boxValue" ng-blur="doneEditing(boxValue)">
when saving, verify the value is different
$scope.doneEditing = function(v) {
if (v !== $scope.persistedValue) // only save when value is different
$scope.persistedValue=v;
}
There is no special option on ng-blur for pre-checking equality that I'm aware of. A simple if seems to do the trick
Newer versions of AngularJS (now in 1.3 beta) supports this natively. See my answer here
The solution that worked for me was as follows:
<input id="fieldId" type="text"
ng-model="form.fields.thisField"
ng-model-options="{ updateOn: 'blur' }"
ng-change="form.save()" />
I found this solution here
That page says it requires AngularJS version 1.3.0+ . I tested it with AngularJS version 1.5.11 and it works in that version for me.
In app.html
<input type="text" name="name" ng-model="$ctrl.user.name" ng-blur="$ctrl.saveChanges()" ng-change="$ctrl.onFieldChange()"/>
in app.ts
public onFieldChange() {
this.isValuesModified = true;
}
//save changes on blur event
public saveChanges() {
//check if value is modified
if(this.isValuesModified){
//Do your stuff here
}
}
This is how I resolved this.
<input ng-focus="oldValue=value" ng-model="value" ng-blur="oldValue!=value && youFunction()" />

Categories

Resources