Focusing an input field in a React component – getting type error trying to create a ref - dom

I have a React component that contains a text <input> element. When the component is mounted, I want the text cursor to be set in the input field, i.e. I want the text input element to have the focus.
In a “traditional” JavaScript React component, I would get the input field's DOM element through a ref and then call its focus method.
I've read this documentation that explains how to use refs in Reason-React: https://github.com/reasonml/reason-react/blob/master/docs/react-ref.md
Alas, the code sample contained on this page is for refs of custom component, it only mentions that it also works on React DOM elements.
So I've tried to convert the example code to a React DOM element, here is what I've tried so far:
type state = {
text: string,
inputElement: ref(option(Dom.element))
};
let valueFromEvent = (evt) : string => (
evt
|> ReactEventRe.Form.target
|> ReactDOMRe.domElementToObj
)##value;
let component = ReasonReact.reducerComponent("EditTodoField");
let setInputElement = (theRef, {ReasonReact.state}) =>
state.inputElement := Js.Nullable.to_opt(theRef);
let make = (~initialText, ~onSubmit, _) => {
...component,
initialState: () => {text: initialText, inputElement: ref(None)},
reducer: (newText, state) => ReasonReact.Update({...state, text: newText}),
render: ({state: {text}, reduce, handle}) =>
<input
value=text
_type="text"
ref=(handle(setInputElement))
placeholder="Todo description"
onChange=(reduce((evt) => valueFromEvent(evt)))
onKeyDown=(
(evt) =>
if (ReactEventRe.Keyboard.key(evt) == "Enter") {
onSubmit(text);
(reduce(() => ""))()
} else if (ReactEventRe.Keyboard.key(evt) == "Escape") {
onSubmit(initialText);
(reduce(() => ""))()
}
)
/>
};
The error message I get is this:
We've found a bug for you!
/Users/pahund/git/todo-list-reason-react/src/EditTodoField.re 21:11-35
19 ┆ value=text
20 ┆ _type="text"
21 ┆ ref=(handle(setInputElement))
22 ┆ placeholder="Todo description"
23 ┆ onChange=(reduce((evt) => valueFromEvent(evt)))
This has type:
ReasonReact.Callback.t(Js.Nullable.t(Dom.element)) (defined as
(Js.Nullable.t(Dom.element)) => unit)
But somewhere wanted:
(Js.null(Dom.element)) => unit
The incompatible parts:
Js.Nullable.t(Dom.element) (defined as Js.nullable(Dom.element))
vs
Js.null(Dom.element)
I know that the problem probably lies within how I define the type of the state at the beginning of the code, it's different for DOM elements than it is for custom components.
What would be the correct type definition here to fix the bug?
The full project can be found here on GitHub: https://github.com/pahund/todo-list-reason-react/tree/ref-problem

I think your reason-react dependency is out of date. refs were changed from Js.null(Dom.element) to Js.nullable(Dom.element) in 0.3.0. See https://github.com/reasonml/reason-react/blob/master/HISTORY.md#030
If for some reason you can't or refuse to upgrade, you can just use Js.Null.to_opt instead though :)
(Also, if you do upgrade, you can use Js.toOption as a nice shortcut in place of Js.Nullable.to_opt)

Related

Reason: Error: Unbound value not__ for bs-jest

Trying to test a binding of lit-html method html
open Jest;
let write = () => LitHtml.html("<div></div>");
open Expect;
describe("LitHtml", () =>
test("#html", () =>
expect(() =>
write()
) |> not_ |> toThrow
)
);
I am told this cryptic error:
Error: Unbound value not__
Hint: Did you mean not_?
Jest.Expect.plainPartial('a) => Jest.Expect.invertedPartial('a)
But clearly wrote not_ as it suggests, not not__.
My attempted binding:
[#bs.module "LitHtml"] [#bs.val]
external html: string => Js.nullable(string) = "html";
let html = htmlStr => html(htmlStr) |> Js.Nullable.toOption;
Thanks for any assistance. Just getting started with ReasonML ;)
Seems like this is caused by a largely undocumented change in Reason 3.3.4. I think it might hide in PR #2197.
not is a keyword in OCaml, which is why Expect.not_ is named such as it is in the first place. And this change seems to "mangle" (ie. translate) not in Reason into not_ in OCaml, and then not_ to not__ and so on.
So the solution is simply to replace all instances of not_ in your code with not. OR you can update bs-jest to 0.4.7 where I've added not__ as an alias to not_, so you can use either not or not_.

Verify if a user typed a word from a ReactiveList with Reactive Extension

I have a ReactiveList with keywords. The user can add or remove keyword from that list. The app needs to verify if the user typed one of the keywords.
There was already a similar post but it doesn't take in account a flexible list:
Using Reactive Extension for certain KeyPress sequences?
var keyElements = new ReactiveList<KeyElement>();
IObservable<IObservable<int>> rangeToMax = Observable.Merge(keyElements.ItemsAdded, keyElements.ItemsRemoved).Select(obs => Observable.Range(2, keyElements.Select(ke => ke.KeyTrigger.Length).Max()));
IObservable<IObservable<string>> detectedKeyTrigger = rangeToMax
.Select(n => _keyPressed.Buffer(n, 1))
.Merge().Where(m => keyElements.Where(ke => ke.KeyTrigger == m).Any());
//Here I want to end up with IObservable<string> instead of IObservable<IObservable<string>>
I can get rid of the outer IObservable by reassigning the detectedKeyTrigger each time an element in the reactive list changes, but then I lose all my subscriptions.
So, how can I end up with just an Observable of strings?
First off, both Max and Any have overloads which takes a selector and a predicate respectively. This negates the need of the Select.
Next, I changed the Observable.Merge to use the Changed property of ReactiveList which is the Rx version of INotifyCollectionChanged. I also changed the Select to produce an IEnumerable of ints instead; it just felt more Right™.
var keyElements = new ReactiveList<KeyElement>();
IObservable<IEnumerable<int>> rangeToMax = keyElements.Changed
.Select(_ => Enumerable.Range(2, keyElements.Max(keyElement => keyElement.KeyTrigger.Length));
IObservable<IObservable<string>> detectedKeyTrigger = rangeToMax.
.Select(range => range
.Select(length => _keyPressed.Buffer(length, 1).Select(chars => new string(chars.ToArray()))) // 1
.Merge() // 2
.Where(m => keyElements.Any(ke => ke.KeyTrigger == m)) // 3
.Switch(); // 4
Create an IObservable<string> which emits the last n characters typed by the user. Create such an observable for each of the possible lengths of an combo
Merge the observables in the IEnumerable<IObservable<string>> into one Observable<string>
Only let strings which mach one of the KeyTriggers through
As rangeToMax.Select produces an IObservable<IObservable<string>> we use Switch to only subscribe to the most recent IObservable<string> the IObservable<IObservable<string>> produces.

SugarCRM 6.5 CE: how to remove button in detailview according to a condition

I'm trying to remove buttons in detail view of a Lead if it is alredy converted.
I saw a similar question and it use javascript to hide buttons. I'm trying to obtain same result via php.
This is my view.detail.php in custom\modules\Leads\views\ folder
class LeadsViewDetail extends ViewDetail {
function __construct(){
parent::__construct();
}
function preDisplay() {
parent::preDisplay();
if($this->bean->converted==1) {
echo "hide";
foreach ($this->dv->defs['templateMeta']['form']['buttons'] as $key => $value) {
unset($this->dv->defs['templateMeta']['form']['buttons'][$key]);
}
} else {
echo "show";
}
}
}
Using this code, after a Quick Repair & Rebuilt, I see "hide" or "show" correctly according to the Lead status but buttons are not updated correctly.
If I open a converted Lead after QR&R, I will never see the buttons.
If I open a unconverted Lead after QR&R, I will see the buttons all times.
I'm stuck with this situation. Can anyone explain me where is the problem? How I can solve it?
Every help is very appreciated.
You can probably handle this without extending the ViewDetail by using Smarty logic ("customCode") in custom/modules/Leads/metadata/detailviewdefs.php. It looks like the Convert button is already only rendered when the user has Edit privileges, so it's not a big deal to add one more condition to it...
$viewdefs['Leads']['DetailView']['templateMeta']['form]['buttons'][] = array('customCode' => '
{if $bean->aclAccess("edit") && $bean->converted}
<input title="{$MOD.LBL_CONVERTLEAD_TITLE}"
accessKey="{$MOD.LBL_CONVERTLEAD_BUTTON_KEY}"
type="button"
class="button"
name="convert"
value="{$MOD.LBL_CONVERTLEAD}"
onClick="document.location=\'index.php?module=Leads&action=ConvertLead&record={$fields.id.value}\'" />
{/if}');
Alternatively, if you do have several conditions and they'd get too messy or difficult for Smarty logic to be reasonable, we can combine a small amount of Smarty Logic with the extended ViewDetail.
This except of custom/modules/Leads/metadata/detailviewdefs.php is actually the out-of-the-box file from SugarCRM CE 6.5.24, where it looks like they've actually tried to make this customization easier by supplying a Smarty var $DISABLE_CONVERT_ACTION. For reference, it simply needs the global config variable disable_convert_lead to be set and enabled, but I suspect that this was a relatively new feature not included in earlier versions. Still, it's a good example of using the View to set a simple Smarty variable that we can pivot on:
<?php
$viewdefs['Leads']['DetailView'] = array (
'templateMeta' => array (
'form' => array (
'buttons' => array (
'EDIT',
'DUPLICATE',
'DELETE',
array (
'customCode' => '{if $bean->aclAccess("edit") && !$DISABLE_CONVERT_ACTION}<input title="{$MOD.LBL_CONVERTLEAD_TITLE}" accessKey="{$MOD.LBL_CONVERTLEAD_BUTTON_KEY}" type="button" class="button" onClick="document.location=\'index.php?module=Leads&action=ConvertLead&record={$fields.id.value}\'" name="convert" value="{$MOD.LBL_CONVERTLEAD}">{/if}',
//Bug#51778: The custom code will be replaced with sugar_html. customCode will be deplicated.
'sugar_html' => array(
'type' => 'button',
'value' => '{$MOD.LBL_CONVERTLEAD}',
'htmlOptions' => array(
'title' => '{$MOD.LBL_CONVERTLEAD_TITLE}',
'accessKey' => '{$MOD.LBL_CONVERTLEAD_BUTTON_KEY}',
'class' => 'button',
'onClick' => 'document.location=\'index.php?module=Leads&action=ConvertLead&record={$fields.id.value}\'',
'name' => 'convert',
'id' => 'convert_lead_button',
),
'template' => '{if $bean->aclAccess("edit") && !$DISABLE_CONVERT_ACTION}[CONTENT]{/if}',
),
),
We can combine this $DISABLE_CONVERT_ACTION reference with a custom/modules/Leads/views/view.detail.php like the following to set it based on whatever condition we want:
<?php
require_once('modules/Leads/views/view.detail.php');
class CustomLeadsViewDetail extends LeadsViewDetail {
/*
* while we might normally like to call parent::display() in this method to
* best emulate what the parnts will do, we instead here copy-and-paste the
* parent methods' content because LeadsViewDetail::display() will set the
* DISABLE_CONVERT_ACTION Smarty var differently than we want.
*/
public function display(){
global $sugar_config;
// Example One: Disable Conversion when status is Converted
$disableConvert = ($this->bean->status == 'Converted');
// Example Two: Disable Conversion when there is at lead one related Call
// where the status is Held
$disableConvert = FALSE;
$this->bean->load_relationships('calls');
foreach($this->bean->calls->getBeans() as $call){
if($call->status == 'Held'){
$disableConvert = TRUE;
break; // exit foreach()
}
}
// Example Three: Disable Conversion if the User is in a specific Role, e.g.
// Interns who are great for data entry in Leads but shouldn't be making
// actual sales
global $current_user;
$disableConvert = $current_user->check_role_membership('No Lead Conversions');
// In any of the above examples, once we have $disableConvert set up
// as we want, let the Smarty template know.
$this->ss->assign("DISABLE_CONVERT_ACTION", $disableConvert);
// copied from ViewDetail::display();
if(empty($this->bean->id)) {
sugar_die($GLOBALS['app_strings']['ERROR_NO_RECORD']);
}
$this->dv->process();
echo $this->dv->display();
}
}

Macro matching a single expr after [expr*], path[tt*] and ident[tt*] branches

I'm trying to make a macro that I can call in the following manner:
mactest!(some::Path[1, 2, AnotherName[3, 4]])
Which would be equivalent to the following:
make_result(
"some::Path",
1.convert(),
2.convert(),
make_result(
"AnotherName",
3.convert(),
4.convert()
)
)
where convert is some trait that will be implemented for a bunch of types. (convert and make_result has the same result type).
This is as far as I've come:
// Note: u32 is used as an example result type.
// The real code attempts to create a more complicated object.
trait Foo {
fn convert(&self) -> u32;
}
fn make_result(name: &str, data: Vec<u32>) -> u32 {
// This example ignores name and makes a meaningless result
data.iter().fold(0,|a, &b| a + b)
}
#[macro_export]
macro_rules! mactest {
( [ $($inner:expr),* ] ) => {{
let mut result = Vec::new();
$(
// Process each element.
result.push(mactest!($inner));
)*
result
}};
($name:path [ $($inner:tt),* ] ) => {
make_result(stringify!($name), mactest!([$($inner),*]))
};
($name:ident [ $($inner:tt),* ] ) => {
make_result(stringify!($name), mactest!([$($inner),*]))
};
// Process single value. This is never matched?
($x:expr) => {
$x.convert()
};
}
The first matching branch of the macro is supposed to match each element of a list to either the path/ident[items] or the single item .convert branch at the end. But the final branch is never reached, with rust complaining error: expected ident, found '1' when single items enter the macro, i.e. mactest!(1).
My reasoning as a beginner rust user is that the macro has four patterns: [expr*], path[tt*], ident[tt*] and expr. When I pass something like 1 into the macro, I don't see why any of the above patterns should match/interfere.
Can someone explain why this doesn't work? Is there a workaround to get the intended result?
macro rules are tried by starting with the first one and going down from there. So if you want to prevent your other rules from triggering in special cases, you need to put the special case rule first.
Try it out in the playground

Lift - how to get default value of select using WiringUI

I have an issue with getting default value of select dropdown.
i have fruits val:
val fruits = List("apple", "banana", "other")
and i render a tr with:
<tr id={ theLine.guid }>
<td>
{
SHtml.ajaxSelect(fruits, Full(fruits(0)),
s => {
mutateLine(theLine.guid) {
l => Line(l.guid, l.name, s, l.note)
}
Noop
})
}
</td>
(...)
on page html is rendered correctly with option selected="selected", but when i try to save it to DB i get empty value of fruit. if i change option to 'other' and then i select it back to 'apple', it saves right value.
i add also a println function to addLine to see what values are in this vector, and there is empty value if i dont change fruit option, so i suppose that it is not problem when i process lines to save it to DB.
can you help me with this?
thanks
Gerard
Before you change your select option, you are not triggering the event that calls your function. The function is bound to onChange and that only gets fired when the value changes.
To fix, you could either: Start with an option like "Select a value". This would require the user to change the item, but is the only way to trigger the onchange.
If you don't want to do that, you could add a button and add your logic to a button click handler that would get called when submitted. Something like this should help - you'll need to bind it to your output, either inline as you provided, or via CSS Selectors:
var selected = Full(fruits(0))
SHtml.ajaxSelect(fruits, selected,
s => {
selected = Full(s)
Noop
})
SHtml.ajaxSubmit("Submit", () => {
mutateLine(theLine.guid) {
l => Line(l.guid, l.name, selected, l.note)
}
})