I'm building a custom block programmatically that has two select boxes. The first one is populated automatically and the second is populated based on the value of the first. Ideally I'd like to use AJAX and I have experience integrating AJAX in a standard form, however the block configuration form doesn't include a $form_state variable and seems to function differently. Can the standard method be used?
'#ajax' => array(
'callback' => 'my_callback',
'wrapper' => 'the-id',
'method' => 'replace',
'effect' => 'fade',
),
How would the callback work?
Thanks,
Howie
I didn't try this but I bet it works: use hook_form_alter() to access your block configuration form. There, you will have a &$form_state and be able to do fancy AJAX stuff (see this).
The hard part is to ONLY alter YOUR form at the alter-hook. Possible ways:
Not sure if this works (most elegant way):
function mymodule_form_alter(&$form,&$form_state,$form_id) {
if ($form_id == 'block_admin_configure' ) {
// Find the delta in the $form variable
if ($form['delta'] == 'the_delta_you_are_looking_for') {
//do fancy ajax stuff
}
}
}
Ugly but definitely possible:
function mymodule_form_alter(&$form,&$form_state,$form_id) {
if ($form_id == 'block_admin_configure' && arg(4) == 'mymodule') {
//do fancy ajax stuff
}
}
}
Even uglier but also possible:
function mymodule_block_configure($delta = '') {
$form = array();
if ($delta == 'my_block') {
$form["my_block_change_this"] = array(
"#type" => "hidden",
"#value" => "lalala",
)
}
}
function mymodule_form_alter(&$form,&$form_state,$form_id) {
if ($form_id == 'block_admin_configure' ) {
if (!empty($form['my_block_change_this'])) {
//do fancy ajax stuff
}
}
}
Tip: Print out the form_state-array (at the alter hook) and see what's there (That's always the first thing I do when I run into FAPI-Issues). Hope this helps.
Related
I am developing a simple CKEditor5 plug-in. Part of the plug-in is a "Command" that executes like this:
execute(options) {
const contentItemUtils = this.editor.plugins.get('ContentItemUtils');
const contentItemElement = contentItemUtils.getClosestSelectedContentItemElement(this.editor.model.document.selection);
this.editor.model.change(writer => {
writer.setAttribute('width', options.width, contentItemElement);
});
}
The problem happens when I call writer.setAttribute. I always get an error like this:
CKEditorError: attribute-operation-attribute-exists {"node":{"attributes":{"contentId":"CORE08954D2EBB7042799E0A059DC90703DD","contentName":"Paris","contentType":"Destination","contentTypeDisplay":"Destination","contentViewing":"draft","categoryLayout":"overview","detailPageId":"","alignment":""},"name":"contentItem"},"key":"width"}
Read more: https://ckeditor.com/docs/ckeditor5/latest/framework/guides/support/error-codes.html#error-attribute-operation-attribute-exists
What I am trying to do -- set a model attribute to a new value -- seems fairly simple.
Is there a restriction about updating model attributes that already have values?
I ended up first removing the attribute and then adding it :
editor.model.change( writer => {
const element = selection.getSelectedElement();
writer.removeAttribute( 'format', element)
editor.model.change( writer => {
writer.setAttribute( 'format', 'date', element)
});
} );
I'm trying to replace or insert an array item (upsert) with existing ngxs state operators. I am currently using the following iif-statement. Is there an easier way to do this?
setState(
patch({
contractList: iif<Contract[]>(
contractList=>
contractList.some(
contract =>
contract.id === contractId
),
updateItem<Contract>(
contract =>
contract.id === contractId,
patch(loadedContract)
),
insertItem<Contract>(loadedContract)
)
})
)
State operators exist but it doesn't mean you're forced to use them, right. There is a recipe called Immutability Helpers. It's going to be published to the main site in 1-2 weeks.
If we're talking about state operators then it's possible to create custom operators, that's some kind of decomposition:
function insertOrUpdateContract(id: string, loadedContract?: Contract) {
return iif<Contract[]>(
contracts => contracts.some(contract => contract.id === id),
updateItem(contract => contract.id === id, patch(loadedContract)),
insertItem(loadedContract)
);
}
Thus there will be less code in your action handler and it will be more self-descriptive:
ctx.setState(
patch({ contractList: insertOrUpdateContract(contractId, loadedContract) })
);
Otherwise you could have used immer library or something else preferred, that would save more code:
import { produce } from 'immer';
const state = produce(ctx.getState(), draft => {
const index = draft.contractList.findIndex(contract => contract.id === contractId);
// It exists then let's update
if (index > -1) {
draft.contractList[index] = loadedContract;
} else {
draft.contractList.push(loadedContract);
}
});
ctx.setState(state);
For sure immer is an additional package for the project but it's very lightweight. It provides more declarative way to do such immutable updates. Choose what suits you best!
I would stop on some immutability helper when it comes to such complex state updates.
I am not a rxjs expert.
I have a method which actually does a save.
this.saveProducts(this.boxes, 'BOXES')
But there are two other different type of items that needs to use the same method for saving , just the parameter is different.
this.saveProducts(this.containers, 'CONTAINER')
In my component I have few other independent saving is happening and all these should happen one by one.
So my method look like this.
return this.service.edit(materials)
.do((data) => {
this.materials= data;
})
.switchMap(() => this.customerService.addCustomer(this.id, this.customers))
.switchMap(() => this.saveProducts(this.boxes, 'BOXES'))
.switchMap(() => this.saveProducts(this.containers, 'CONTAINER'))
.switchMap(() => this.saveProducts(this.books, 'BOOKS'))
.do(() => {
.......
}
But whats happening is it never calls the second saveProducts method unless I have a return from first one
private saveProducts(products: IProduct[], type:
Type): Observable<any> {
console.log(type);
}
Thanks for anyone who looked at it..
The answer to this issue is to return an empty observable if nothing is there to save.
if (products$.length === 0) {
return Observable.of([]);
}
Thanks guys.. Happy coding..
Heres the problem
I want to require a field (litters_per_year) only if another field that is a checkbox is checked. When I do this, cake is trying to force me to put a value into the field and I don't know why. I have tried setting required & allowEmpty to false & true respectively, but then my custom rule does not run.
Heres the code
NOTE: The details of the following code aren't that important - they are here to provide a scenario.
I have the following code in my VIEW which works fine:
echo $this->Form->input('litters_per_year', array(
'label' => 'Litters per year (average)'
));
I have the following code in my MODEL's public $validate:
'litters_per_year' => array(
'isNeeded' => array(
'rule' => array('isNeeded', 'litters_per_year'),
'message' => 'Please enter the litters per year average'
)
)
which is calling the custom validation method
public function isNeeded($field) {
// Check if a checkbox is checked right here
// Assume it is not... return false
return false;
}
It returns false for simplicity to solve this issue.
Let's assume that the checkbox field is named 'the_checkbox'.
At the moment your field should always fail validation, since you return false from isNeeded.
To make it work as you expect, do something like this:
(Note: replace 'ModelName' with your model name)
public function isNeeded($field) {
if ($this->data['ModelName']['the_checkbox']) {
// Checkbox is checked, so we have to require litters per year
if (empty($field)) {
// They have checked the box but have NOT entered litters_per_year
// so we have a problem. NOT VALID!
return false;
} else {
// They have checked the box, and entered a litters_per_year
// value, so all good! Everything is valid!
return true;
}
} else {
// Checkbox is not checked, so we don't care what
// the field contains - it is always valid.
return true;
}
}
Or, without the needless verbosity, this should work:
public function isNeeded($field) {
if ($this->data['ModelName']['the_checkbox']) {
return $field;
} else {
return true;
}
}
In this example, if the checkbox is checked, validation will pass if $field is truthy, and fail if it is falsey.
I try to delete entries in the database with the active records of Yii. But I think it works really weird.
I want to delete all records of my table where vehicle_id = the given id and plug_id NOT IN (given string)
I tried a lot of ways and nothing worked but this
$query = "delete from `vehicle_details` where `vehicle_id`= ".$vehicle->id." AND `plug_id` NOT IN (".implode(',', array_map(function($item) {
return $item->type;
}, $vehicleDetails->plug)).")";
$command = Yii::app()->db->createCommand($query);
$command->execute();
But why isn't this working???
VehicleDetail::model()->DeleteAllByAttributes(
array('vehicle_id' => $vehicle->id),
array('condition' => 'plug_id NOT IN (:ids)',
'params' => array('ids' => implode(',', array_map(function($item) {
return $item->type;
}, $vehicleDetails->plug)))));
Or this:
VehicleDetail::model()->deleteAll(' vehicle_id = :vehicleId AND plug_id NOT IN (:ids)', array('vehicleId' => $vehicle->id, 'ids' => implode(',', array_map(function($item) {
return $item->type;
}, $vehicleDetails->plug))));
But if I make and Find by attributes out of this query it works well and returns the correct data.
I hope you can explain it to me.
Your code does not work because of wrong arguments passed in the methods. Problems occur when YII tries to build CDbCriteria object using your array arguments. Fortunetly, you can build a CDbCriteria by yourself and pass it into methods directly. Guess in this particular case it will be easier to use a CDbCriteria object to solve the issue.
$dbc = new CDbcriteria();
$dbc->condition = 'vehicle_id = :vehicleId';
$dbc->params = array(':vehicleId'=>$vehicle->id);
$dbc->addNotInCondition(
'plug_id',
array_map(
function($item) {
return $item->type;
},
$vehicleDetails->plug)
);
VehicleDetail::model()->deleteAll($dbc);
That is all you need.