I am trying to programmatically remove the required status from some node/*/edit fields. The required status is set by default and should only be removed based on the selected value in a taxonomy field:
Using the following code:
function mymodule_form_node_form_alter(&$form, &$form_state, $form_id) {
if( $form_id == 'job_node_form' ){
$form['#after_build'] = array('test');
}
}
function test(&$form, &$form_state) {
$form['title']['#required'] = FALSE; // works
$form['jobs_schools_data']['field_job_type']['#required'] = FALSE; // does NOT work
return $form;
}
I find I can successfully remove the required status from the title field but the same does not work for the fields I added like field_job_type in the code example above.
Why doesn't this work, and how I might correct it?
I had something similar, in the actual form I did not set the required status on two fields, only one is supposed to show if certain criteria was met. I then created a custom function to check if a field was populated and based on that I then set the error message from there.
function events_registration_form_validate($form, &$form_state){
$selected_time = $form_state['values']['time_selected'];
if ($form_state['values']['time_selected'] == '') {
form_set_error('time_selected', t('Please choose an event time.'));
Related
this is how the data is displayed but i want
Rhugveda desai -> flowers,Sarees,Prasad
In my application i need to use group by clause . But i am getting a syntax error.Also, What should i do if i want quantity column to be multiplied by amount to get the total? My tables are inkind and inkind_items, where inkind.id is foreign key in inkind_items table as inkind_id.
SQLSTATE[42000]: Syntax error or access violation: 1055 Expression #11
of SELECT list is not in GROUP BY clause and contains nonaggregated
column
my inkind_items tabel is inkind_items
my inkind table is inkind
My query is:
$inkinds = DB::table('inkind')
->join('inkind_items', 'inkind.id', '=', 'inkind_items.inkind_id')
->select('inkind.*', 'inkind_items.*')
->groupBy('inkind_items.inkind_id')
->get();
Try using group_concat()
$inkinds = DB::table('inkind')
->join('inkind_items', 'inkind.id', '=', 'inkind_items.inkind_id')
->select('inkind.*', DB::raw('group_concat(inkind_items.name) as items'))
->groupBy('inkind_items.inkind_id')
->get();
Here I'm assuming inkind have field name and inkind_items has fields items.
You can use Laravel collection methods for that.
Just call:
$inkinds->groupBy('inkind_id');
after ->get(). Considering that inkind_id is unique column for both tables
Hi. You asked another question earlier today (about displaying an input when a particular checkbox is checked) but deleted it before I submitted my answer, so I thought I would paste the answer here instead:
Just to get you started, here is an explanation of how to use
addEventListener and createElement to achieve your desired result.
If any part of it is still unclear after studying the code and the
accompanying comments, please search for the name of the still-unclear function on
MDN.
(For example, https://developer.mozilla.org/en-US/docs/Web/API/Document/getElementsByClassName.)
// Sets `box1` to refer to the first element on the page with the class "box".
const box1 = document.getElementsByClassName("box")[0];
// Sets `container` to be the element whose id attribute has the value "container".
// (This is where our new input element, inside a new label element, will be added.)
const container = document.getElementById("container");
// Begins listening for clicks. From now on, whenever the user clicks anywhere
// on the page, our listener will call the `noticeClick` function.
document.addEventListener("click", noticeClick);
function noticeClick(event){
// Because this function's name is the second argument in
// the call to `document.addEventListener`, above, it is
// automatically called every time a 'click' event happens on the
// page (and it automatically receives that event as an argument.)
// The "target" of the event is whatever the user clicked on.
// So we set the variable `targ` to refer to this target, and we check whether:
// 1) the clicked target is our checkbox,
// 2) this click caused the checkbox to gain the "checked" attribute, and
// 3) we have not already given the checkbox the "added" class
const targ = event.target;
if(targ.id == "box1" && targ.checked && !targ.classList.contains("added")){
// If all three conditions are met, we...
// ...set two variables, `label` and `val`
const label = event.target.id;
const val = event.target.value;
// ...call the `createInput` function, passing these variables as its two arguments
createInput(label, val);
// ...give the checkbox the "added" class (so we can avoid accidentally adding it again)
targ.classList.add("added");
}
}
function createInput(newLabel, newValue){
// When the user has checked our checkbox, the `noticeClick` function
// will call this function, which receives two arguments (which we can
// see, by examining the `noticeClick` function, are two strings: the
// `id` attribute of box1 and the `value` attribute of box1.)
// We use `document.creatElement` to create an `input` element and a
// `label` element, and `document.createTextNode` to set some text
// to be used in the label (using the "newLabel" argument.)
const myInput = document.createElement("input");
const myLabel = document.createElement("label");
const myLabelText = document.createTextNode(newLabel + " ");
// We set our new `input` element's value using the "newValue" argument.
myInput.value = newValue;
// We use `appendChild` to put both the text and the input element
// inside the label, and to put the label inside `container`.
myLabel.appendChild(myLabelText);
myLabel.appendChild(myInput);
container.appendChild(myLabel);
}
// This process can be applied to multiple checkboxes on the same page
// by adding a loop inside the `noticeClick` function, where the string
// "box1" should be replaced with a variable that can refer to the id of a
// different checkbox's `id` for each iteration of the loop.
<label>
<input type="checkbox" id="box1" class="box" value="value1" />
Label for box1
</label>
<hr />
<div id="container"></div>
I am working on an AngularJS application, and have a dialog which is used to update what is displayed on one of the widgets on a particular page.
The widget is a table, and I want to allow the user to display different data types/ HTML elements in particular cells of the table.
As it currently stands, the cells will display text, a button, or a 'tag' which is a variable taken from the system, and displayed as a particular element using the ngTagsInput library. As the user types into the text input field, the autocomplete feature displays a list of 'tags' that match what has been typed so far- when the user clicks one of these tags in the list, that is when it is then displayed as a tag in the table widget (if they just type the full name of the tag without selecting it from the autocomplete list, it is just displayed as text).
I have made some changes so that when the first character that the user types is a :, the autocomplete list should show a list of the various pages of the application, for which a button linking to that page will be added to the table (i.e. typing page1 would just show the text "page1" in the table cell, but typing :, and selecting 'page1' from the autocomplete list would display a button linking to page1). This functionality currently works as expected,.
I now want to be able to add multiple buttons to a single table cell (assuming that the user inputs the tags into the text field in the dialog in the correct format). The format to do this will be the same as for one button, but with a ; separating each of the individual buttons- for example, to add buttons for page1 & page2 to the same table cell, the user should just type: :page1;page2.
The autocompleteTagsFilter() function, which is what displays the list of available tags/ pages based on what the user has typed so for (i.e. filters the list of available options as the user continues typing) is defined with:
$scope.autocompleteTagsFilter = function(query) {
if (query.startsWith(":")) {
// Split the 'query' search string on ':', to use only the string
var buttonsQuery = query.substring(1);
if (!buttonsQuery && lastTagQryKw) {
buttonsQuery = lastTagQryKw;
}
/*check whether the buttonsQuery variable contains a ';' - if it does, split it */
if(buttonsQuery.includes(';')) {
//console.log("buttonsQuery includes ; - ", buttonsQuery);
var btnsToDisplay = buttonsQuery.split(";");
console.log("btnsToDisplay: ", btnsToDisplay);
}
// Declare variables to be used in 'for' loop
var userPages = pagesPresets;
var page;
var result = [];
// 'For' loop should iterate through the list of user pages,
// and remove path, so that only the page name is shown
for (page in userPages) {
page = userPages[page];
// If the page key starts with 'buttonsQuery', and its length
// is greater than 6 (i.e. just 'pages/' shouldn't be displayed)
// add the page to the list of pages to be displayed.
if (page.key.startsWith(buttonsQuery) && page.key.length > 6) {
result.push(page.key);
//console.log("result- after result.push(page.key): ", result);
} else {
//console.log("userPages: ", userPages);
}
};
for (btn in btnsToDisplay) {
console.log("btn: ", btnsToDisplay[btn]);
result.push(btnsToDisplay[btn]);
console.log("button added to result: ", result);
if (result.length > 0) {
lastTagQryKw = query;
}
// Return the list of pages that match what the user has typed
//console.log("RESULT (when query.startsWith(':') is true) is: ", result);
return result;
// Otherwise, if the user types something that does not start with ':',
//then it should be a tag- search for tags that match this term
} else {
if (!query && lastTagQryKw) {
query = lastTagQryKw;
}
var result = Object.keys(fxTag.getTags()).filter(function(name) {
return name.indexOf(query.toLowerCase()) !== -1;
});
if (result.length > 0) {
lastTagQryKw = query;
}
console.log("RESULT (when query.startsWith(':') is false) is: ", result);
return result;
}
};
As I type, if I start typing with a :, then the browser console displays a list of pages that I could add a button for:
RESULT (when query.startsWith(':') is true) is: (4) ["pages/userpage1", "pages/pagetitle", "pages/userpage2", "pages/auth"]
If I select one of these buttons, that button is correctly added, and when I click the 'Preview' button on the dialog, the dialog closes and the page shows the table with the working button correctly added to the table cell. The console also shows the output:
tag is only one tag: {tag: "pages/auth"}
which comes from the function onAddingTagItem()
However, if I continue typing, rather than selecting one of the buttons shown by the autocomplete, and type out two buttons, i.e.
:pages/userpage1;pages/userpage2
then rather than displaying two buttons, the table cell just displays the text, i.e :pages/userpage1;pages/userpage2.
and I get the console output:
value of tags: (2) [":pages/userpage1", "pages/userpage2"]
The same onAddingTagItem() function is called in both cases, only it runs the if instead of the else when the text entered by the user contains a ;. This function is defined with:
$scope.onAddingTagItem = function(tag) {
if(tag.tag.includes(";")) {
var tags = tag.tag.split(";");
console.log("value of tags: ", tags);
angular.forEach(tags, function(tag, key) {
if(tag.startsWith(":")) {
tag = tag.split(":")[1];
}
angular.extend(tag, toTagItemObj(tag));
})
} else {
console.log("tag is only one tag: ", tag);
}
return true;
};
So, as I understand, it should be returning the tags whether there are one or several of them, because it returns true whether the if or the else is run.
Given that this function always returns true, whether it's the if or the else that has been run, why is it that a button is only displayed when I click an option from the autocomplete list, and not when typing out a button in full?
How can I ensure that the text entered will be displayed as a button when it meets the criteria (starts with a :), even if it's not selected from the autocomplete list, or enable selection of multiple items from the autocomplete list?
I have a table that displays several entries, each has an <input>. The user can dynamically add additional inputs by clicking an "add entry" button. I need to iterate over them before saving and validate each one. I simplified my example to check that the value of each input is greater than 100 (ultimately I will use a pattern-match to validate MAC and IP addresses).
I can probably handle it if I could select all <input>s, but I would really like to select a specific <input> using an index I already have in my scope. I read that angular.element is a way, but I need to select something that was dynamically created, and thus not named something easy like id="myInput". Unless I use an id of "input" and append a unique number with Angular's $index in the id attribute?
Here is my Fiddle that shows what I'm doing. Line 44 is an if() that should check if any <input> is greater than 100. The "Save Row" button validates that the input is greater than 100, but if you edit a line, I need the "Save" button to validate any that the user has edited (by clicking Edit next to it).
tl;dr:
How can I use Angular to select an <input> that has been created dynamically?
I have updated your fiddle in a clean way so that you can maintain the validation in a generic method for both add & edit.
function validateBinding(binding) {
// Have your pattern-match validation here to validate MAC and IP addresses
return binding.ip > 100;
}
Updated fiddle:
https://jsfiddle.net/balasuar/by0tg92m/27/
Also, I have fixed the current issue with editing you have to allow multiple editing without save the first row when clicking the next edit on next row.
The validation of 'save everything' is now cleaner in angular way as below.
$scope.changeEdit = function(binding) {
binding.onEdit = true;
//$scope.editNum = newNum;
$scope.showSave = true;
};
$scope.saveEverything = function() {
var error = false;
angular.forEach($scope.macbindings, function(binding) {
if(binding.onEdit) {
if (validateBinding(binding)) {
binding.onEdit = false;
} else {
error = true;
}
}
});
if (error) {
alert("One/some of the value you are editing need to be greater than 100");
} else {
$scope.showSave = false;
}
}
You can check the updated fiddle for the same,
https://jsfiddle.net/balasuar/by0tg92m/27/
Note: As you are using angular, you can validate the model as above and no need to retrieve and loop the input elements for the validation. Also for your case, validating the model is sufficient.
If you need some advanced validation, you should create a custom
directive. Since, playing around with the elements inside the
controller is not recommended in AngularJS.
You can use a custom class for those inputs you want to validate. Then you can select all those inputs with that class and validate them. See this Fiddle https://jsfiddle.net/lealceldeiro/L38f686s/5/
$scope.saveEverything = function() {
var inputs = document.getElementsByClassName('inputCtrl'); //inputCtrl is the class you use to select those input s you want to validate
$scope.totalInputs = inputs.length;
$scope.invalidCount = 0;
for (var i = 0; i < inputs.length; i++){
if(inputs[i].value.length < 100){
$scope.invalidCount++;
}
}
//do your stuff here
}
On line 46 a get all the inputs with class "classCtrl" and then I go through the input s array in order to check their length.
There you can check if any of them is actually invalid (by length or any other restriction)
I have form where I can add up to 30 option fields (option[1], option[2], ...) and for now Im using 'option.*' => 'required' rule in request validation but with this there is a little problem, if you submit form with all option fields empty it shows long error message with each option field required, but I need that it shows only one message for all options like: "Each option field is required".
Any ideas how to make it?
Thanks!
I found a solution to this. I will post it here in case somebody needs it:
Basically, you need to override formatErrors method in your request validation class
protected function formatErrors(Validator $validator)
{
$errors = parent::formatErrors($validator);
// this will remove the keys that have index larger than 0
$keys = array_filter(array_keys($errors), function($item) {
$parts = explode('.', $item);
// you might want to modify this to match your fields,
// I had another level of keys
if (count($parts) === 3 and is_numeric($parts[1]) and (int)$parts[1] > 0) {
return false;
}
return true;
});
$errors = array_intersect_key($errors, array_flip($keys));
return $errors;
}
So my question is: how do I scan the JSON in angular to find the first instance of isPrimary:true and then launch a function with the GUID that is in that item.
I have a webservice whos JSON defines available Accounts with a display name and a GUID this generates a dropdown select list that calls a function with the GUID included to return full data from a web service.
In the scenario where theres only 1 OPTION I dont show the SELECT and simply call the function with the single GUID to return the data from the service. If theres no options I dont show anything other than a message.
Code below shows what I currently have.
The Spec has now changed and the data they are sending me in the first service call which defines that select list is now including a property isPrimary:true on one of the JSON object along with its GUID as per the rest
I now need to change my interface to no longer use the SELECT list and instead fire the function call to the service for the item that contains the isPrimary:true property. However there may be multiple instances where isPrimary:true exists in the returning JSON so I just want to fire the function on the first found instance of isPrimary:true
Equally if that property isnt in any of the JSON items then just fire the function on the first item in the JSON.
My current Code is below - you can see the call to retrieve the full details is from function:
vm.retrieveAccount(GUID);
Where the GUID is supplied with each JSON object
Code is:
if (data.Accounts.length > 1) {
vm.hideAcc = false;
setBusyState(false);
//wait for the user to make a selection
} else if (data.Accounts.length == 1){
vm.hideAcc = true;
// Only 1 acc - no need for drop down get first item
vm.accSelected = data.Accounts[0].UniqueIdentifier;
vm.retrieveAccount(vm.accSelected);
} else {
// Theres no accounts
// Hide Drop down and show message
setBusyState(false);
vm.hideAcc = true;
setMessageState(false, true, "There are no Accounts")
}
Sample of new JSON structure
accName: "My Acc",
isPrimary: true,
GUID: "bg111010101"
Still think that's a weird spec, but simple enough to solve. Just step through the array and return the first isPrimary match. If none are found, return the first element of the array.
var findPrimary = function(data) {
if (!(Array.isArray(data)) || data.length == 0) {
return false; // not an array, or empty array
}
for (var i=0; i<data.length; i++) {
if (data[i].isPrimary) {
return data[i]; // first isPrimary match
}
}
// nothing had isPrimary, so return the first one:
return data[0];
}