How to compose props across component in reason-react bindings? - material-ui

I am currently writing a material-UI reason-react binding and I want to know how I can re-use previously define Props.
The Select component spreads all of the Input props into itself, in the underlying react-js lib. this is done by spreading props however this is discouraged in ReasonML as typings are lost.
As a temporary solution, I have copied the props from one to another but this is not scalable. I would appreciate if someone could suggest what is the correct way of doing this in Reason-React?
Thanks
Input module definiton:
module Input = {
[#bs.module "material-ui/Input"] external reactClass : ReasonReact.reactClass = "default";
let make =
(
~disableUnderline: option(bool)=?,
~disabled: option(bool)=?,
~error: option(bool)=?,
~autoFocus: option(bool)=?,
~fullWidth: option(bool)=?,
~style: option(ReactDOMRe.style)=?,
~value: option(string)=?,
~onChange: option((ReactEventRe.Form.t => unit))=?,
~placeholder: option(string)=?,
~className: option(string)=?,
~inputType: option(string)=?,
children
) =>
ReasonReact.wrapJsForReason(
~reactClass,
~props=
Js.Nullable.(
{
"disableUnderline": unwrap_bool(disableUnderline),
"disabled": unwrap_bool(disabled),
"error": unwrap_bool(error),
"fullWidth": unwrap_bool(fullWidth),
"autoFocus": unwrap_bool(autoFocus),
"style": from_opt(style),
"placeholder": from_opt(placeholder),
"className": from_opt(className),
"type": from_opt(inputType),
"value": from_opt(value),
"onChange": from_opt(onChange)
}
),
children
);
};
Select module definiton:
module Select = {
[#bs.module "material-ui/Select"] external reactClass : ReasonReact.reactClass = "default";
let make =
(
~autoWidth: option(bool)=?,
~classes: option(Js.t({..}))=?,
~className: option(string)=?,
~displayEmpty: option(bool)=?,
~input: option(ReasonReact.reactElement)=?,
~inputClasses: option(Js.t({..}))=?,
~native: option(bool)=?,
~multiple: option(bool)=?,
~menuProps: option(Js.t({..}))=?,
~renderValue: option((unit => unit)),
~value: option('a)=?,
~style: option(ReactDOMRe.style)=?,
/* Input Props*/
~disableUnderline: option(bool)=?,
~disabled: option(bool)=?,
~error: option(bool)=?,
~autoFocus: option(bool)=?,
~fullWidth: option(bool)=?,
~value: option(string)=?,
~onChange: option((ReactEventRe.Form.t => unit))=?,
~placeholder: option(string)=?,
~className: option(string)=?,
~inputType: option(string)=?,
children
) =>
ReasonReact.wrapJsForReason(
~reactClass,
~props=
Js.Nullable.(
{
"autoWidth": unwrap_bool(autoWidth),
"classes": from_opt(classes),
"className": from_opt(className),
"displayEmpty": unwrap_bool(displayEmpty),
"input": from_opt(input),
"InputClasses": from_opt(inputClasses),
"native": unwrap_bool(native),
"multiple": unwrap_bool(multiple),
"MenuProps": from_opt(menuProps),
"renderValue": from_opt(renderValue),
"value": from_opt(value),
"style": from_opt(style),
/* Input Props*/
"disableUnderline": unwrap_bool(disableUnderline),
"disabled": unwrap_bool(disabled),
"error": unwrap_bool(error),
"fullWidth": unwrap_bool(fullWidth),
"autoFocus": unwrap_bool(autoFocus),
"style": from_opt(style),
"placeholder": from_opt(placeholder),
"className": from_opt(className),
"type": from_opt(inputType),
"value": from_opt(value),
"onChange": from_opt(onChange)
}
),
children
);
};

You can use currying and Js.Obj.assign to achieve this:
let common = (reactClass, props, ~commonProp1, ~commonProp2, children) =>
ReasonReact.wrapJsForReason(
~reactClass,
~props=Js.Obj.assign(props, {
"commonProp1": commonProp1,
"commonProp2": commonProp2
}),
children
);
module Input = {
[#bs.module "material-ui/Input"] external reactClass : ReasonReact.reactClass = "default";
let make = (~inputProp1, ~inputProp2) => common(reactClass, {
"inputProp1": inputProp1,
"inputProp2": inputProp2
});
};
module Select = {
[#bs.module "material-ui/Select"] external reactClass : ReasonReact.reactClass = "default";
let make = (~selectProp1, ~selectProp2) => common(reactClass, {
"selectProp1": selectProp1,
"selectProp2": selectProp2
});
};
In each make function common is partially applied, and due to currying will "extend" the make function with its own arguments. In effect, the type signature of e.g. Input.make will be ~inputProp1 => ~inputProp2 => ~commonProp1 => ~commonProp2 => ....

Related

Sysmfony 4.4 - Testing form with 'contraints' in options of TimeType generate an UndefinedOptionsException

I made a form in Symfony 4.4 with a TimeType field defined like this :
$builder
->add('planned_start', TimeType::class, [
'widget' => 'single_text',
'empty_data' => '',
'constraints' => [
new NotBlank([
'message' => 'worksheet.worker.planned_start.required_error'
])
]
])
Functional tests of my controller work perfectly and return the defined error if no valid is given.
But,when I'm testing the form directly, I get the following exeception like I can't put any constraint on the TimeType field
Symfony\Component\OptionsResolver\Exception\UndefinedOptionsException: An error has occurred resolving the options of the form "Symfony\Component\Form\Extension\Core\Type\TimeType": The option "constraints" does not exist. Defined options are: "action",
"allow_file_upload", "attr", "attr_translation_parameters", "auto_initialize", "block_name", "block_prefix", "by_reference", "choice_translation_domain", "compound", "data", "data_class", "disabled", "empty_data", "error_bubbling", "help",
"help_attr", "help_html", "help_translation_parameters", "hours", "html5", "inherit_data", "input", "input_format", "label", "label_attr", "label_format", "label_translation_parameters", "mapped", "method", "minutes", "model_timezone", "placeholder",
"post_max_size_message", "property_path", "reference_date", "required", "row_attr", "seconds", "translation_domain", "trim", "upload_max_size_message", "view_timezone", "widget", "with_minutes", "with_seconds".
/var/www/vendor/symfony/form/ResolvedFormType.php:99
/var/www/vendor/symfony/form/FormFactory.php:76
/var/www/vendor/symfony/form/FormBuilder.php:94
/var/www/vendor/symfony/form/FormBuilder.php:244
/var/www/vendor/symfony/form/FormBuilder.php:195
/var/www/vendor/symfony/form/FormFactory.php:30
/var/www/tests/Form/WorksheetWorkerFormTypeTest.php:39
Here is how I test the form :
<?php
namespace App\Tests\Form\Type;
use App\Form\Type\TestedType;
use App\Form\WorksheetWorkerFormType;
use Doctrine\Persistence\ObjectManager;
use Symfony\Component\Form\PreloadedExtension;
use Symfony\Component\Form\Test\TypeTestCase;
// ...
class WorksheetWorkerFormTypeTest extends TypeTestCase
{
private $objectManager;
protected function setUp()
{
// mock any dependencies
$this->objectManager = $this->createMock(ObjectManager::class);
parent::setUp();
}
protected function getExtensions()
{
// create a type instance with the mocked dependencies
$type = new WorksheetWorkerFormType($this->objectManager);
return [
// register the type instances with the PreloadedExtension
new PreloadedExtension([$type], []),
];
}
public function testMinimal()
{
$form = $this->factory->create(WorksheetWorkerFormType::class);
$form->submit([
'planned_start' => '08:00',
'planned_end' => '16:00'
]);
$this->assertTrue($form->isValid());
}
}
Any Idea ?
Thanks

transformation on simple json array

Im trying to update json array. If i have JSON like this one:
{
"value":[
{
"name":{
"first":"Bob",
"last":"Pegelano"
},
"age":31,
"email":"bob#gmail.com"
},
{
"name":{
"first":"Majkl",
"last":"Skot"
},
"age":321,
"email":"gecko#gmail.com"
}]
}
I can easily update an array like this.
val jsarrayUpdate = (__ \ 'value).json.update(
__.read[JsArray].map{ o => o :+ Json.obj( "field243" -> "coucou" ) }
)
myJson.transform(jsarrayUpdate)
But I have simple array JSON without any key:
[{
"name":{
"first":"Bob",
"last":"Pegelano"
},
"age":31,
"email":"bob#gmail.com"
},
{
"name":{
"first":"Majkl",
"last":"Skot"
},
"age":321,
"email":"gecko#gmail.com"
}]
And was hoping to be able to edit it with this command:
val jsarrayUpdate2 = __.json.update(
__.read[JsArray].map{ o => o :+ Json.obj( "field243" -> "coucou" ) }
)
This is not working nor is anything else I tried in the past two hours. What am I doing wrong?
Thank you.
What about
jsArray.as[List[JsObject]].map {i => i ++ Json.obj( "field243" -> "coucou")}
This will give you a List[JsObject]. If you need you can convert it back to JsArray with
Json.toJson(listOfJsObjects).as[JsArray]

How to populate Yii2 Autocomplete with AJAX call

I am trying to switch to Yii2 from Yii 1.1. This was source attribute of TextAreaJuiAutoComplete widget
'source'=>"js:function(request, response) {
$.getJSON('".$url"', {
term: extractLast(request.term)
}, response);
}",
This is not working in Yii2 with yii\jui\AutoComplete anymore. Can anyone give me a hint what is the cause? Underlying JavaScript objects should be the same.
If I put following code it works, but I want to use ajax calls instead.
'source' => [ "c++", "java", "php", "coldfusion", "javascript", "asp", "ruby" ],
Try this:
use yii\web\JsExpression;
.....
.....
'source'=>new JsExpression("function(request, response) {
$.getJSON('".$url."', {
term: request.term
}, response);
}"),
Try this:
AutoComplete::widget([
'name'=>'myacfield',
'clientOptions' => [
'source' => Url::to(['autocomplete']),
'minLength'=>'2',
],
'options'=>[
'class' => 'form-control'
]
]);
But your AutoComplete action must return a one dimensional array like
...
$rs = Yii::$app->db->createCommand($sql)->queryAll();
$row_set = [];
foreach ($rs as $row)
{
$row_set[] = $row['name']; //build an array
}
echo json_encode($row_set); //format the array into json data
Examle with like.
Controller:
public function actionSearch($term)
{
\Yii::$app->response->format = \yii\web\Response::FORMAT_JSON;
$rs = Cure::find()->where(['like', 'name_uz', $term])->all();
if($rs !=null){
$row_set = [];
foreach ($rs as $row)
{
$row_set[] = $row->name_uz; //build an array
}
return $row_set;
}else{
false;
}
}
In view:
<? use yii\jui\AutoComplete;?>
<?= AutoComplete::widget([
'model' => $model,
'attribute' => 'country',
'options' => ['class' => 'form-control'],
'clientOptions' => [
'source' => Url::to(['cure/search']),
'minLength'=>'2',
],
]); ?>

need help in html::tagFilter

I wrote a filter like this in perl
my $tf = HTML::TagFilter->new(
allow => {
img => { src => [] },
b => { all => [] },
i => { all => [] },
em => { all => [] },
u => { all => [] },
s => { all => [] }
}
);
$message_body = $tf->filter($message_body);
now what I needed from this filter to do is allowing the given tags, and for img to allow the src attribute. The code gives great results except for tag like this <img src="cid:img.png" alt="Smiley face"> it just return <img> instead of <img src="sid:imp.png"> which is what I want, does any one here knows why?!
The reason your src attribute isn't being passed through is because of the module's cross-site scripting protection. The value cid:img.png is rejected as an invalid URL, and so the attribute is removed.
The tidiest way to get around this is to extend the list of valid protocols to include cid, like this:
my #protocols = $tf->xss_permitted_protocols;
push #protocols, 'cid';
$tf->xss_permitted_protocols(#protocols);
$message_body = $tf->filter($message_body);
If you set log_rejects => 1 when you create the HTML::TagFilter object then you can examine the values returned by $tf->report to see the module's reasons for rejecting each component of the HTML.
You need to set skip_xss_protection to 1:
#!/usr/bin/perl
use strict;
use warnings;
use HTML::TagFilter;
my $tf = HTML::TagFilter->new(
allow => {
img => {src => []},
b => { all => [] },
i => { all => [] },
em => { all => [] },
u => { all => [] },
s => { all => [] }
},
skip_xss_protection => 1,
);
my $html = qq{<img src="cid:img.png" alt="Smiley face">};
$html = $tf->filter($html);
print $html;
prints:
<img src="cid:img.png">

Lithium: Remove related documents in a remove-Filter of a model

I have a Games model which hasMany Avatars and Agents. When i remove the game, i want to clean up all the remaining data, so i also want to remove all Avatars and Agents with the corresponding game_id:
namespace app\models;
use app\models\Avatars;
use app\models\Agents;
class Games extends \lithium\data\Model
{
public static function __init($options = array()) {
parent::__init($options);
$self = static::_instance(__CLASS__);
Games::applyFilter('remove', function($self, $params, $chain) {
$conditions = array( 'game_id' => $params['conditions']['_id'] );
$message = new \app\extensions\helper\Message();
$debugString = var_export($conditions, true);
$message->addDebugMessage("params:{$debugString}");
//Output:
//params:array ( 'game_id' => '4f301f69a170c8cf52000002', )
if(!Agents::remove($conditions)) { $message->addErrorMessage('Es konnten nicht alle Agents geloescht werden.'); };
if(!Avatars::remove($conditions)) { $message->addErrorMessage('Es konnten nicht alle Avatare geloescht werden.'); };
return $chain->next($self, $params, $chain);
});
}
}
Though the game gets removed, agents and avatars remain in the MongoDB.
Does someone have a hint on this?
Example of agent in db
>db.agents.find()
{ "_id" : ObjectId("4f301f71a170c8391f000000"), "game_id" : ObjectId("4f301f69a170c8cf52000002"), "type" : "army", "subtype" : "deer", "units" : 5, "xPos" : 5, "yPos" : 5 }
I'd have to look, but I don't think remove() casts values. You'd need to do:
$conditions = array( 'game_id' => new MongoId($params['conditions']['_id']));