Drupal webform programmatically creating composite components - drupal-7

I have successfully created a custom webform component for a specific type of data. The data comes in various sections so I need to create additional but existing component types which will be sub-components to my custom component.
Basically, I am trying to create a composite of webform components programmatically.
My problem is, the code all executes successfully - I get my custom component gets created and I get feedback saying my sub-components were also successfully created.
However, the sub-components do not show-up in my webform.
At the point when my custom component is created and inserted in to the DB, I am attempting to create all the necessary sub-components via the following code:
function my_module_webform_component_insert($component){
if($component['type'] == 'my_component'){
$node = node_load($component['nid']);
$address_fields = array("my_sub_component1", "my_sub_component2");
foreach ($address_fields as $key => $address_field) {
//fetch next available component ID
$next_id_query = db_select('webform_component')->condition('nid', $component['nid']);
$next_id_query->addExpression('MAX(cid) + 1', 'cid');
$next_cid = $next_id_query->execute()->fetchField();
_create_sub_component($component['nid'], $address_field, $next_cid, $component['cid']);
}
}
}
My _create_sub_component function is defined below:
function _create_sub_component($nid, $new_component, $cid, $pid){
$node = node_load($nid);
$processed_name = str_replace(' ', '_', strtolower($new_component));
// Create the webform components array. Not sure if we need all these
// values, but let's be sure.
$component = array(
'cid' => (int)$cid,
'pid' => 0,#(int)$pid,
'nid' => (int)$node->nid,
// I don't trust the admin to make a key based on input :)
'form_key' => $processed_name,
'name' => $new_component,
// I want all lines to be numeric type component.
'type' => 'textfield',
'value' => '',
'extra' => array(),
'mandatory' => '0',
'weight' => 0,
'page_num' => 1,
);
array_push($node->webform['components'], $component);
node_save($node);
drupal_set_message("{$new_component} component successfully created in {$node->title}");
}
My guess is the call to node_save is causing the problem but I don't know exactly how.

Got it!
Replace:
array_push($node->webform['components'], $component);
node_save($node)
With:
webform_component_insert($component);

Related

react using .map() inside component prop attribute

I'm trying to conditionally render a component based on if that user is online. The problem im experiencing is with the status prop on my MessengerFriend component. In my conditional statement it is returning both 'online' and 'offline', but when I only use the equivalent if It returns the online status correctly. My initial thought was that using onlineFriends.map() is returning both statuses because there are in fact users both online and offline.
const [friendsList, setFriendsList] = useState([])
const [onlineFriends, setOnlineFriends] = useState([])
let mappedFriendChatList = friendsList.map(friend => {
return(
<MessengerFriend
key={friend.friend_id}
value={friend}
status={onlineFriends.map(onlineFriend => onlineFriend.userId == friend.user_id ? 'online' : 'offline')}
/>
)
})
I'm expecting to have a list of all my friends and there status 'online' and 'offline' to display correctly
Map is the wrong method for this use-case; some is what you need.
{onlineFriends.some(onlineFriend => onlineFriend.userId === friend.user_id) ? 'online' : 'offline'}
You could also use filter with a length check on the result array, or find (which would work identically to some in this case).

Render items from an array of objects, using another array with ids: ts error and best practice

I'm doing a library project (see the link for more explaination) with react, redux-toolkit and typescript.
I'm doing an addToWishlist funcionality: the user simply click on the star, the code dispatches the action which patch the database and the state is updated. There is another action to remove the book from the list.
In <Wishlist /> component I want of course render the titles and other data of each book. So I useAppSelector to retrieve wishlist and the entire list of books, and with help of filter and map I would get the corresponding books, but I'm not able to render them, because it gives me a ts error: index.d.ts(1373, 9): The expected type comes from property 'children' which is declared here on type 'DetailedHTMLProps<HTMLAttributes<HTMLUListElement>, HTMLUListElement>'
Important note about the structure
wishlist is an array of strings, which are the ids of the books.
The entire list of books instead, is an array of objects: every object is a book, with title, author, id etc.
My questions:
What is going wrong here?
Basically, is it better to save in the wishlist array the ids (like I've done) and so I've to retrieve the data of books in my <Wishlist /> component with filter and map, or is it better to save the entire book (or at least, the infos that I need), so that is more easy to have the data?
Which is the best practice, to render the list, in the first case, where I have to retrieve data using filter and map?
Here is the attempt:
const Wishlist = () => {
const dispatch = useAppDispatch();
const wishlist = useAppSelector(state => state.user.userInfo.wishlist);
const list = useAppSelector(state => state.catalogue.list);
const renderBooks = () => {
return wishlist.map(wishId => {
list.filter(book => {
if (book.id === wishId) {
return (
<li>{book.title}</li>
)
}
})
})
}
return (
<ul>
{renderBooks()}
</ul>
)
};
export default Wishlist;
Edited - maybe the problem is not the concatenation of methods, but something in the function calling?
Another more little example, with another solution, but same problem
interface fakeObj {
id: string;
name: string;
}
const fakeIds = ['5', '12', '878'] // this is my 'wishlist', containing only ids
const fakeList: fakeObj[] = [ // this is my 'list', containing objects, which were single books
{ id: '334', name: 'Tony' },
{ id: '5', name: 'Milly' },
{ id: '12', name: 'Jack' },
{ id: '7', name: 'Stew' },
{ id: '878', name: 'Mark' },
{ id: '87998', name: 'Greg' }
]
const renderArray = () => {
const arr: fakeObj[] = [];
fakeIds.forEach(id => {
fakeList.filter(obj => {
if (obj.id === id) {
arr.push(obj)
}
})
})
return arr; // the returned array is what I want: an array continaing the objects that i need
}
return (
<ul>
{/* The problem actually is here! */}
{renderArray()}
</ul>
)
You're not using .filter() or .map() correctly, which is what's causing the errors.
The comment above that you rejected was the correct solution to this problem.
The added edited example is even more confusing, because you're no longer trying to return renderable elements, as the return is an array of objects, which can't be rendering in a UL.
So, I'm going back to the initial example.
Problem #1:
You're trying to return a JSX element within the callback passed to .filter(). This is not how .filter() works. You're supposed to pass a callback to .filter() that will return TRUE or FALSE dependent on whether or not you want the element to be filtered in or filtered out. E.g. listOfCounts.filter( count => count > 5); would return a modified array of all elements in listOfCounts that are greater than 5.
Problem #2:
You're currently calling the .map() first, which is wrong, but then also calling the .filter() WITHIN the .map() instead of chaining the map AFTER the filter. Because right now, your .map() call is not even attempting to return a list of JSX elements representing the book titles you want to render. These calls are meant to be chained opposite of how you have it.
An example of how to use .map() that's very simple:
listOfNames.map(name => <div>{name}</div>);
A simple example combining .map() with .filter():
listOfNames.filter(name => name[0] == 'J').map(name => <div>{name}</div>);
which filters out all names that don't start with an uppercase J.
The solution above that Mr. Rafique provided you is correct / very close to correct depending on exact implementation, so I'd suggest you combine this added info with his solution.

Testing UI updates caused by state changes

I'm having a create-react-app and am trying to test a functional component. The component makes a service call on initial mount and sets the state and populates a material ui select based on the results.
Now, the user can pick one of those options on the select and I make another service call to retrieve more data based on that.
I am able to mock the initial service call, return mock values and prove that the select got populated but I am having trouble with finding the mocked option text in the select. I get an error - Unable to find an element with the text. This could be because the text is broken up by multiple elements.
Here is how my test looks like:
const fetchMock = jest.spyOn(window, 'fetch').mockImplementation(() =>
Promise.resolve({ ok: true, json: () => Promise.resolve([{itemName:
'Color', itemId: 23},
{itemName: 'Car', itemId: 34}])}));
const localhostURL = "http://localhost:8080";
let retrieveByLabelTextFunction;
let retrieveByRole;
act(() => { const { getByLabelText } =
render(<ItemSelection/>);
retrieveByLabelTextFunction = getByLabelText;
retrieveByRole = getByRole;
});
expect(fetchMock).toBeCalledWith(localhostURL + '/items/');
findByDisplayValue("Color");
findByDisplayValue("Car");
const items = retrieveByLabelTextFunction("Items");
fireEvent.mouseDown(items);
const itemListbox = within(retrieveByRole("listbox"));
fireEvent.click(itemListbox.getByText("I34 - Car"));
Any thoughts on how to test this scenario? I34 in the text is added by the code under test appending the id basically.

How to apply CRUD golden-layout with redux and reactjs?

I have built a golden layout wrapper using golden-layout-react-redux, and I'm creating new layouts and connecting them to the panels reducer so every time I add a new panel it will get built inside the GoldenLayoutWraper. I connected the config content like this:
componentDidMount() {
const config = {
content: [{
type: "column",
content: this.props.panels//here
}]
};
goldenlayout = new GoldenLayout(config, this.layout);
goldenlayout.registerComponent(
"IncrementButtonContainer",
wrapComponent(IncrementButtonContainer, this.context.store)
);
}
goldenlayout.init();
But I'm facing a problem that it is not updating when I add new panel to the reducer, the panels reducer array increases but the contentIems are not.
So I was forced to do it like this:
render() {
this.props.panels.map(panel =>
setTimeout(() => {
goldenlayout.root.contentItems[0].addChild(panel);
}, 100)
);
return (
<div className="goldenLayout" ref={input => (this.layout = input)} />
);
}
this code is inside the GoldenLayoutWrapper component render(), and the problem here is that:
1- Every time I add new panel it adds multiple panels, because it is adding panels.length plus the already existed items in contentItems[0].
2- when I delete all the panels I can not add anymore because the way golden-layout works that when there is only one child it replaces it and become the root child not the contentItems[0] so this goldenlayout.root.contentItems[0].addChild(panel) throws an error.
I also tried to use this react-golden-layout instead but I'm stuck with this issue
P.S: I'm a beginner

Define global array constant for using in view

I want to define global array constant
code in bootstrap.php
$adv_types = array('top' => 'Верх', 'left' => 'Левое', 'right' => 'Правое', 'bottom' => 'Нижнее');
code in view file
echo $form->input('Adv.type', array('type' => 'select', 'option' => $adv_types, 'label' => 'Место рекламы'));
but cakephp gives error:
"Undefined variable: adv_types"
Unfortunately, the scope for bootstrap.php is bootstrap.php, so the $adv_types variable will be out-of-scope as soon as PHP completes parsing bootstrap.php.
There are several approaches you can take, depending on your actual requirements.
Solution 1: you need this variables in many of your views
If you need the variable to be available in all views, you should define and set it in AppController::beforeRender().
In app/app_controller.php:
class AppController extends Controller
{
function beforeRender()
{
parent::beforeRender();
$adv_types = array('top' => 'Верх', 'left' => 'Левое', 'right' => 'Правое', 'bottom' => 'Нижнее');
$this->set(compact('adv_types'));
}
}
This will allow any of your views to access the $adv_types array.
Solution 2: you might need access to this variable anywhere within your CakePHP app
If you must access the $adv_types variable elsewhere in your app, you can add it to the Configure collection. In bootstrap.php:
Configure::write('NameOfYourAppAsNamespace.adv_types', array('top' => 'Верх', 'left' => 'Левое', 'right' => 'Правое', 'bottom' => 'Нижнее'));
I recommend using the name of your project as a pseudo namespace; adv_types is unlikely to conflict with other identifiers, but if you start using this approach more often, your chances of creating conflicts increases. Additionally, this allows you to group the data you're storing in the Configure collection under one namespace, which can be handy for debugging.
Anyway, this approach will allow you to access the variable in any scope under the CakePHP umbrella, by invoking Configure::read(). Thus:
$adv_types = Configure::read('NameOfYourAppAsNamespace.adv_types');
Solution 3: you absolutely must have this variable available as a global variable
If you absolutely must have this as a standard PHP global variable, you could do the following:
$GLOBALS['adv_types'] = array('top' => 'Верх', 'left' => 'Левое', 'right' => 'Правое', 'bottom' => 'Нижнее');
Before doing this, please consider whether this is strictly necessary. Global variables are a really messy business, and you should have a clear and present need to justify it.
Edit/Update!
Thirty seconds in Google Translate has led me to discover that your array contains translations corresponding to the keys. You might want to investigate using CakePHP's Internationalization & Localization features to abstract away the distinction between English and Russian terms for top/left/right/bottom (and everything else).
These need to set in your app_controller.php, and then passed to your views
// app_controller.php
class AppController extends Controller {
var $adv_types = array('top' => 'Верх', 'left' => 'Левое', 'right' => 'Правое', 'bottom' => 'Нижнее');
function beforeFilter() {
$this->set('adv_types', $this->adv_types);
}
}
To me, bootstrap.php is not the correct file for this constant

Resources