Getting form controls from FormController - javascript

I need a way to loop through the registered controls of an AngularJS form. Essentially, I'm trying to get all the $dirty controls, but there's no array of the controls (the FormController has a number of different properties/functions in addition to containing the controls themselves - each as its' own object).
I've been looking at the source code, and I see that there is a controls array in the FormController that is exactly the array I'm looking for. Is there a way to get access to this value, or extend the FormController to include a function that returns this controls array?
Edit: Plnkr demo
Also, I realized that technically I could check the first character in the key string for "$", but I'd like to avoid that in case the FormController/directive changes in a future version of Angular.
Edit 2: Another clarification: My goal in all of this is to be able to determine which specific fields are $dirty, whether by looping through the entire list of controls (not including the $dirty, $invalid, $error, $name, and other properties that live in the Form object as it is) or by extending the FormController and creating a function that returns only the controls which are currently dirty (and not equal to their starting values)
Edit 3: The solution I'm looking for needs to be applicable to forms/models of different structures. The models on the scope are generated via AJAX, so their structure is already set (I'd like to avoid having to hardcode a new structure for all the data I'm already receiving via AJAX). Also, I'm looking to use this form submission process across multiple forms/models, and each of them have differing JSON structures - as they apply to different entities in our Object Model. This is why I've chosen to ask for a way to get access to the controls object in the FormController (I'll post the code from FormController below), because it's the only place where I can get a flat array of all of my fields.
function FormController(element, attrs) {
var form = this,
parentForm = element.parent().controller('form') || nullFormCtrl,
invalidCount = 0, // used to easily determine if we are valid
errors = form.$error = {},
controls = [];
// init state
form.$name = attrs.name || attrs.ngForm;
form.$dirty = false;
form.$pristine = true;
form.$valid = true;
form.$invalid = false;
parentForm.$addControl(form);
// Setup initial state of the control
element.addClass(PRISTINE_CLASS);
toggleValidCss(true);
// convenience method for easy toggling of classes
function toggleValidCss(isValid, validationErrorKey) {
validationErrorKey = validationErrorKey ? '-' + snake_case(validationErrorKey, '-') : '';
element.
removeClass((isValid ? INVALID_CLASS : VALID_CLASS) + validationErrorKey).
addClass((isValid ? VALID_CLASS : INVALID_CLASS) + validationErrorKey);
}
/**
* #ngdoc function
* #name ng.directive:form.FormController#$addControl
* #methodOf ng.directive:form.FormController
*
* #description
* Register a control with the form.
*
* Input elements using ngModelController do this automatically when they are linked.
*/
form.$addControl = function(control) {
controls.push(control);
if (control.$name && !form.hasOwnProperty(control.$name)) {
form[control.$name] = control;
}
};
/**
* #ngdoc function
* #name ng.directive:form.FormController#$removeControl
* #methodOf ng.directive:form.FormController
*
* #description
* Deregister a control from the form.
*
* Input elements using ngModelController do this automatically when they are destroyed.
*/
form.$removeControl = function(control) {
if (control.$name && form[control.$name] === control) {
delete form[control.$name];
}
forEach(errors, function(queue, validationToken) {
form.$setValidity(validationToken, true, control);
});
arrayRemove(controls, control);
};
// Removed extra code
}
As you can see, the form itself has the controls array privately available. I'm wondering if there's a way for me to extend the FormController so I can make that object public? Or create a public function so I can at least view the private array?

For a direct solution to the question, modify #lombardo's answer like so;
var dirtyFormControls = [];
var myForm = $scope.myForm;
angular.forEach(myForm, function(value, key) {
if (typeof value === 'object' && value.hasOwnProperty('$modelValue') && value.$dirty)
dirtyFormControls.push(value)
});
The array 'dirtyFormControls' will then contain the form controls that are dirty.
You can also use this trick to show error messages on form submission for 'Required' validations and all others. In your submit() function you will do something like;
if (form.$invalid) {
form.$setDirty();
angular.forEach(form, function(value, key) {
if (typeof value === 'object' && value.hasOwnProperty('$modelValue'))
value.$setDirty();
});
//show user error summary at top of form.
$('html, body').animate({
scrollTop: $("#myForm").offset().top
}, 1000);
return;
}
And in your form you will show error messages with
<span ng-messages="myForm['subject-' + $index].$error" ng-show="myForm['subject-' + $index].$dirty" class="has-error">
<span ng-message="required">Course subject is required.</span>
</span>
The above solution is useful when you have dynamically generated controls using 'ng-repeat' or something similar.

You can use the following code to iterate the controls:
var data = {};
angular.forEach(myForm, function (value, key) {
if (value.hasOwnProperty('$modelValue'))
data[key] = value.$modelValue;
});

try simply with from within your controller:
$scope.checkForm = function(myFormName){
console.log(myFormName.$invalid);
}
UPDATE:
<div ng-controller="MyController">
<form name="form" class="css-form" novalidate>
<input type="text" ng-model="user.uname" name="uname" required /><br />
<input type="text" ng-model="user.usurname" name="usurname" required /><br />
<button ng-click="update(form)">SAVE</button>
</form>
</div>
app.controller('MyController',function($scope){
$scope.user = {};
$scope.update = function (form){
var editedFields = [];
angular.forEach($scope.user, function(value, key){
if(form[key].$dirty){
this.push(key + ': ' + value);
}
}, editedFields);
console.log(editedFields);
}
});

I've come up with a somewhat decent solution, but it still isn't what I was looking for. I've salvaged some code from another problem involving creating JSON objects from strings, and come up with the following:
Essentially I'm naming my fields in the same way they're tied to the model, and then building a new object for submission when the form_submit is called.
Plnkr demo
In the demo, if you change either of the form fields, then hit submit, you'll see the object pop up with only the dirty values.

Related

How to use a call multiple varibles in a sinlge if statement?

I have multiple variables like so
var a0001 = document.getElementById("a0001");
var a0002 = document.getElementById("a0002");
var a0003 = document.getElementById("a0003");
that I use for
a0001.blur= function() {
localStorage.setItem("a0001", a0001.innerHTML);
};
a0002.blur = function() {
localStorage.setItem("a0002", a0002.innerHTML);
};
a0003.blur = function() {
localStorage.setItem("a0003", a0003.innerHTML);
};
My question is I will be working with 100's of variables and I cant figure out how to place a "loop place holder" to process the variables like such (*** represent the place holder that will cycle throw the variables and parse them out)
***.blur= function() {
localStorage.setItem("***", ***.innerHTML);
};
I am obviously new and this is a single page localstorage project and not sure if I just am not using the right keywords to find the documentation I am looking for or ? so any help will be appreciated.
edit; markup added below
<table>
<thead>
<tr>
<th id="a0001" contenteditable='true' spellcheck="false">ID
</th><th id="a0002" contenteditable='true' spellcheck="false">First Name
</th><th id="a0003" contenteditable='true' spellcheck="false">Last Name
</th></tr></thead>
</table>
The used markup is not entirely clear, but I'm assuming you have a form with some input elements (because blur fires only on form controls and window objets by default).
Despite of the real HTML, you're making a lot of unnecessary work. You've a ton of elements, which you need to handle as a group of elements, not one by one. Using id to identify a group is error prone and also very hard to maintain, and on the top of that, a global variable of each id is created, which floods yet the strongly crowded object with unneeded variables. Also, hundreds of event listeners are not needed, you can use a single listener, which can handle all the elements. Here's a simple code showing how to handle a group of elements:
const form = document.querySelector('#list'),
storingList = new Map(),
inputs = form.querySelectorAll('.to-storage');
inputs.forEach((input, idx) => {
const itemName = 'a' + (idx.toString()).padStart(4, '0');
storingList.set(input, itemName);
});
form.addEventListener('focusout', e => {
if (e.target.className !== 'to-storage') {return;} // Quit, not a target element blurred
const itemName = storingList.get(e.target),
value = e.target.value;
console.log(itemName, value);
// localStorage.setItem(itemName, value); // Commented out to not populate localStorage of SO visitors
});
<form id="list">
Item 1 <input class="to-storage"><br>
Item 2 <input class="to-storage"><br>
Item 3 <input class="to-storage"><br>
Item 4 <input class="to-storage"><br>
Item 5 <input class="to-storage"><br>
Item 6 <input class="to-storage"><br>
Item 7 <input>
</form>
In the snippet, all the inputs which of values are stored in localStorage, are grouped with a class named to-storage. The grouped inputs and the identifiers are collected to a Map object, hence they're easy to identify in an event handler (Map object can take an element as a key). Notice the dynamic item name creation, which takes care of the running index automatically.
The focusout event listener is delegated (blur doesn't bubble and can't be delegated in a simple way) to the form, which contains all the input elements. This way you can edit and update the HTML of the page, no matter how many inputs needs to be removed or added, you don't have to make any changes to the JavaScript which stores the values.
If your markup is different and you can't apply the snippet, please add an example of your markup to the question.
You can have your variables in an array like const arr = [] then loop through each of them. The entire thing would look something like:
const arr = []
// add variables to array
arr.forEach(a => {
a.blur = function() {
localStorage.setItem(a.key, a.innerHTML);
}
})
You may want to define a unique identifier for the variable, like key or something
Do not need to create multiple variables!
Native JS
var elements = document.querySelectorAll('[id^=a000]');
Jquery
$('[id^="a000"]')
Selects elements which IDs start with a000
I will suggest two methods to solve this.
Select element by different ids and store in a array
Ex:
// Add more id of elements on this array
let selectableElementsId = ['a0001', 'a0002', 'a0003'];
selectableElementsId.forEach(elementId => {
let element = document.querySelector(`#${elementId}`);
if(!element) return;
element.blur = function(){
localStorage.setItem(element.id, element.innerHTML);
}
});
add same class to all three or more elements.
// Replace yourClassName with class name of elements
let selectedElements = Array.from(document.querySelectorAll(".yourClassName"));
selectedElements.forEach(element => {
element.blur = function(){
localStorage.setItem(element.id || selectedElements.indexOf(element), element.innerHTML);
}
});

Accept hex only input

I am trying to create an input box that only accepts Hex characters [A-Fa-f0-9].
I know there are a lot of options but have found none that ticks all my checkboxes.
Need to restrict actual user input (so characters other than A-Fa-f0-9 will NOT be allowed entry/keypress/paste
I know this can be done through keyup/keydown/onchange event, but I need this to be enabled globally, more elegant solution
Another option is the input pattern, but this still allows user entry of 'invalid' characters, will just display later on that the validation has failed.
My thoughts:
Top choice: If it is possible to extend the input type:
<input type="hex">
If possible to have a vue directive for this (supposing q-input is my component):
<q-input v-hex>
So I created this vue directive that removes all non-hex characters and converts letters it to uppercase:
Vue.directive('hex', {
update: function (el, binding) {
console.log('Input ' + binding.value)
var newVal = binding.value.replace(/[^a-fA-F0-9\n\r]+/g, '')
console.log('New Hex Only Val = ' + newVal.toUpperCase())
binding.value = newVal
}
})
But it doesn't update the parameter passed in the v-model (parameter holding the binding.value).
Here is my fiddle:
https://jsfiddle.net/keechan/nade9cy0/3/
Sample input -> expected output:
123abc -> 123ABC
12hf23dn -> 12F23D
How can I update its respective v-model parameter? Please note that this directive will be used globally so no hardcoding anywhere.
Anyone help?
Thanks
Vue.directive('hexonly', {
bind(el, binding, vnode) {
const vm = vnode.componentInstance;
el.__unbindHexonly = vm.$watch('value', value => {
vm.__emitValue(value.replace(/[^a-fA-F0-9\n\r]+/g, '').toUpperCase());
}, { immediate: true });
},
unbind(el) {
el.__unbindHexonly && el.__unbindHexonly();
}
});
https://jsfiddle.net/6bw0zvdm/
Quasar uses some deferred model update magic, thus using specific private method __emitValue (source)
By the way, you don't need to specify directive expression ("txt"), v-hexonly will work.

Concept - Creating client side validation from PHP object

I am making my own MVC framework and I was thinking of a way to do "automatic" client side validation controller.
Among other functionalities, my forms, elements and validators are objects that work together somewhat like this (inside the form object):
$this->addElement('text', 'myInput');
$this->elements['myInput']->addValidators(array
'length' => array('min' => 5, 'max' => 10),
'number' => array('decimals' => 0)
));
In the example above, I created a text input named 'myInput' whose value, according to the validators I added:
Must be >= than 5 characters
Must be <= than 10 characters
Must be a number
Must have no decimals (int only)
When I get the form submission and call a validation function, all works well on the server side. However, what bothered me was having to redo validation on the client side manually. I dislike having to duplicate the same functionality so I came up with a way to create my client side validation from the PHP form object that is already there.
It boils down to having JS validator functions that have the same functionality as the PHP validators and calling a getClientValidatiors() function on elements to create the appropriate <script> in the body which attaches the JS events.
NOTE: Please ignore JS errors, I wrote this as a concept and didn't test anything just yet.
The JS validator functions would work like this:
function lengthValidator(options, value, id){
//Validate according to the options. Return true if valid or false otherwise as well as calling printError function with the message and the id
}
function numberValidator(options, value, id){
//Validate according to the options. Return true if valid or false otherwise as well as calling printError function with the message and the id
}
function printError(error, id){
//Might add more functionality later
document.getElementById(id).innerHTML = error;
}
For example, this is what it would look like in the view:
<?php echo $this->form->elements['myInput]; //The HTML ?>
<?php echo $this->form->elements['myInput]->getClientValidators(); //The JS ?>
Prior to form submission, the result would look like:
<input type="text" name="myInput" id="myInput"/>
<span class="error" id="myInput-error"></span>
<script>
document.getElementById('myInput').addEventListener('blur', function(e){
var value = e.value;
var id = e.id + '-error';
if(lengthValidator({min:5, max:10}, value, id) != true){
return;
}
if(numberValidator({decimals:0}, value, id) != true){
return;
}
});
</script>
I'm looking for a thumbs up or suggestions on how this could work with another technique. If you have any thoughts, I'd like to hear them!
Consider writing the validation specification in such a way that you can automatically validate in JavaScript and PHP.
$input_schema = array(
"foo" => array(
"type" => "number",
"decimals" => 0,
"length" => array(
"min" => 5,
"max' => 10
)
)
);
Then in JS you can do:
var input_schema = <?php echo json_encode($input_schema);?>;
function validate_input(form_values) {
for (var key in input_schema) {
validate_property(input_schema[key], form_values[key]);
}
}
function validate_property(schema_property, value) {
if (schema_property.type === "number") {
validate_number(schema_property, value); // etc
}
}
And you can make a similar implementation in PHP.

Using dynamic "non Domain" Arguments in Typo3 Extbase Controller

Form-Elements added by JavaScript to a Fluid-Form are not passed as Argument.
How to do it the right way?
How to dynamically add Form-Fields with JavaScript?
Expected behavior:
Form is submmitted to "subscribeSubmitAction"
Validation of "subscribeSubmitAction" fails
Fallback to "subscribeAction" is called
"$subscription" is set and assigned (NOT HAPPENING) - instead "$subscription" is allways NULL (but subscription.test is set in the Fluid-Form)
My Guess:
The PropertyMapper is removing "subscription.childs" because they are not in "TrustedProperties" - How can I add them?
Why "$subscription" is allways NULL - I have no Idea!
Controller:
namespace VENDOR\EXTENSION\Controller;
class EventController extends \TYPO3\CMS\Extbase\Mvc\Controller\ActionController
{
public function initializeAction()
{
if ($this->arguments->hasArgument('subscription'))
{
//Try to add "subscription.childs" to the PropertyMapperConfiguration
$subscriptionConfiguration = $this->arguments->getArgument('subscription')->getPropertyMappingConfiguration();
$subscriptionConfiguration->allowProperties();
$subscriptionConfiguration->forProperty('*')->allowAllProperties();
$subscriptionConfiguration->forProperty('*')->allowCreationForSubProperty('*');
$subscriptionConfiguration->forProperty('*')->forProperty('*')->allowAllProperties();
$subscriptionConfiguration->forProperty('*')->forProperty('*')->allowCreationForSubProperty('*');
$subscriptionConfiguration->forProperty('childs')->allowProperties();
$subscriptionConfiguration->forProperty('childs')->allowCreationForSubProperty('*');
$subscriptionConfiguration->forProperty('childs.*')->allowProperties();
$subscriptionConfiguration->forProperty('childs.*')->allowCreationForSubProperty('*');
print_r($this->arguments->getArgument('subscription')->getValue()); // => NULL
}
}
/**
* subscribeAction
* #param array $event
* #param array $subscription
* #ignorevalidation $subscription
*/
public function subscribeAction($event,$subscription = null)
{
print_r($subscription); // => NULL
$this->view->assign('event',$event);
$this->view->assign('subscription',$subscription);
}
/**
* subscribeSubmitAction
* #param array $event
* #param array $subscription
* #param string $g_recaptcha_reponse
* #validate $g_recaptcha_reponse NotEmpty
*/
public function subscribeSubmitAction($event,$subscription = null, $g_recaptcha_reponse = null)
{
/**
* This Method will never be called because the Validation of "$g_recaptcha_reponse" must fail (it's empty)
*/
}
}
Template:
<f:debug>{subscription}</f:debug>
<f:form action="subscribeSubmit" name="subscription" object="{subscription}">
<f:form.hidden name='event[]' value='{event.uid}' />
<f:form.textfield property="test" />
<!--suppose this was added by javascript-->
<input type="text" name="tx_extension_plugin[subscription][childs][0][test]" value="{subscription.childs.0.test}">
<f:form.hidden name='g_recaptcha_reponse' value='' />
<f:form.submit name="submit" value="Submit" />
</f:form>
I would recommend to check the request via Network panel of your browser. Do you see the arguments there? Do they follow the naming schema of tx_extension_plugin[subscription][...?
They should be contained in the request within extbase.
Perhaps you shouldn't add them to the existing argument subscription but instead create another argument. You can validate it the same way and errors should be available the same way within Fluid.
You can be right with "TrustedProperties" I'm not sure about that. But if so, you definitely should be able to workaround that within your initializeAction method.

How can I streamline/improve/beautify my auto-form JS code?

I've got a Javascript (dojo) function that I'm using to auto-populate a form. The idea is to pass in a JS object with keys for the form field ids, and values that help define how to fill in the field.
It works, but it feels a bit ugly to me (the switch statement, the objects within objects syntax). Any thoughts on how can I improve* this code?
/**
* Fill in the form with passed in values
*
* #param {Object} defaults
*
* Can be used to pass in default values to the form. Expects an object like this:
* {<field id>: {type: '<field type>', value:'<value>'}
*
* for example:
* {
* paymethod: {type: 'select', value:'Account Transfer'}, // html select
* fundsource: {type: 'combo', value:'Corporation Account'} // dijit.comboBox
* }
*/
function fillDefaults(defaults) {
var props;
for (var field in defaults) {
props = defaults[field];
switch (props['type']) {
// so far only select and combo have been implemented
// others will be added as needed
// and maybe grouped depending on how they work
// (e.g. all dijits together, <input> with <select>, etc.)
case 'select':
dojo.byId(field).value = props['value'];
dojo.byId(field).onchange()
break;
case 'combo':
dijit.byId(field).attr('value', props['value']);
break;
}
}
}
[*] improve: make prettier, more js-like, more dojo-like, more streamlined
What does pretty mean? What's js-like?
A switch, although memory intensive, is cleaner if you plan to extend with more objects.
Maybe, instead of a switch, have an object containing your functions:
funcs = {
select: function(value) {
dojo.byId(field).value = value;
dojo.byId(field).onchange()
},
combo: function(value) {
dijit.byId(field).attr('value', value);
}
};
function payFillDefaults(defaults) {
var props;
for(var field in defaults) {
props = defaults[field];
if(funcs[props['type']]) {
funcs[props['type']](props['value']);
}
}
}
Improved a bit according JSLint standards:
function fillDefaults(defaults) {
var props, field;
for (field in defaults) {
props = defaults[field];
switch (props.type) {
case 'select':
dojo.byId(field).value = props.value;
dojo.byId(field).onchange();
break;
case 'combo':
dijit.byId(field).attr('value', props.value);
break;
}
}
}
Take a look at dojox.form.manager — it does approximately what you want to achieve. It support existing form widgets ("main" mixin), practically all DOM form elements (node mixin), the unified event processing, and so on. It can be used with unmodified forms.
As you would probably guessed already it is structured as a set of independent mix-ins so you can select only required functionality. For convenience and as an example there a class, which combines all mix-ins together: Manager.
Read what it does, see if it fits your needs, if not, study its code and borrow what you like. If it misses something — share your feedback on the mailing list (on gmane.org). Or, if you can improve it, contribute back.

Categories