1 /*
2 * Copyright (c) 2021-2022 Huawei Device Co., Ltd.
3 * Licensed under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License.
5 * You may obtain a copy of the License at
6 *
7 * http://www.apache.org/licenses/LICENSE-2.0
8 *
9 * Unless required by applicable law or agreed to in writing, software
10 * distributed under the License is distributed on an "AS IS" BASIS,
11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 * See the License for the specific language governing permissions and
13 * limitations under the License.
14 */
15
16 #include "bridge/declarative_frontend/jsview/js_textpicker.h"
17
18 #include "base/log/ace_scoring_log.h"
19 #include "bridge/common/utils/engine_helper.h"
20 #include "bridge/declarative_frontend/engine/functions/js_function.h"
21 #include "bridge/declarative_frontend/jsview/js_datepicker.h"
22 #include "bridge/declarative_frontend/jsview/js_interactable_view.h"
23 #include "bridge/declarative_frontend/jsview/js_view_abstract.h"
24 #include "bridge/declarative_frontend/jsview/js_view_common_def.h"
25 #include "bridge/declarative_frontend/jsview/models/textpicker_model_impl.h"
26 #include "bridge/declarative_frontend/view_stack_processor.h"
27 #include "core/components/picker/picker_base_component.h"
28 #include "core/components/picker/picker_theme.h"
29 #include "core/components_ng/pattern/text_picker/textpicker_model.h"
30 #include "core/components_ng/pattern/text_picker/textpicker_model_ng.h"
31 #include "core/pipeline_ng/pipeline_context.h"
32
33 namespace OHOS::Ace {
34
35 std::unique_ptr<TextPickerModel> TextPickerModel::textPickerInstance_ = nullptr;
36
GetInstance()37 TextPickerModel* TextPickerModel::GetInstance()
38 {
39 if (!textPickerInstance_) {
40 #ifdef NG_BUILD
41 textPickerInstance_.reset(new NG::TextPickerModelNG());
42 #else
43 if (Container::IsCurrentUseNewPipeline()) {
44 textPickerInstance_.reset(new NG::TextPickerModelNG());
45 } else {
46 textPickerInstance_.reset(new Framework::TextPickerModelImpl());
47 }
48 #endif
49 }
50 return textPickerInstance_.get();
51 }
52 } // namespace OHOS::Ace
53
54 namespace OHOS::Ace::Framework {
55
JSBind(BindingTarget globalObj)56 void JSTextPicker::JSBind(BindingTarget globalObj)
57 {
58 JSClass<JSTextPicker>::Declare("TextPicker");
59 MethodOptions opt = MethodOptions::NONE;
60 JSClass<JSTextPicker>::StaticMethod("create", &JSTextPicker::Create, opt);
61 JSClass<JSTextPicker>::StaticMethod("defaultPickerItemHeight", &JSTextPicker::SetDefaultPickerItemHeight);
62 JSClass<JSTextPicker>::StaticMethod("onAccept", &JSTextPicker::OnAccept);
63 JSClass<JSTextPicker>::StaticMethod("onCancel", &JSTextPicker::OnCancel);
64 JSClass<JSTextPicker>::StaticMethod("onChange", &JSTextPicker::OnChange);
65 JSClass<JSTextPicker>::StaticMethod("backgroundColor", &JSDatePicker::PickerBackgroundColor);
66 JSClass<JSTextPicker>::StaticMethod("onClick", &JSInteractableView::JsOnClick);
67 JSClass<JSTextPicker>::StaticMethod("onTouch", &JSInteractableView::JsOnTouch);
68 JSClass<JSTextPicker>::StaticMethod("onKeyEvent", &JSInteractableView::JsOnKey);
69 JSClass<JSTextPicker>::StaticMethod("onDeleteEvent", &JSInteractableView::JsOnDelete);
70 JSClass<JSTextPicker>::StaticMethod("onAppear", &JSInteractableView::JsOnAppear);
71 JSClass<JSTextPicker>::StaticMethod("onDisAppear", &JSInteractableView::JsOnDisAppear);
72 JSClass<JSTextPicker>::Inherit<JSViewAbstract>();
73 JSClass<JSTextPicker>::Bind(globalObj);
74 }
75
Create(const JSCallbackInfo & info)76 void JSTextPicker::Create(const JSCallbackInfo& info)
77 {
78 if (info.Length() >= 1 && info[0]->IsObject()) {
79 auto paramObject = JSRef<JSObject>::Cast(info[0]);
80 auto getSelected = paramObject->GetProperty("selected");
81 auto getValue = paramObject->GetProperty("value");
82 JSRef<JSArray> getRange = paramObject->GetProperty("range");
83 std::vector<std::string> getRangeVector;
84 std::string value = "";
85 uint32_t selected = 0;
86 if (getRange->Length() > 0) {
87 if (!ParseJsStrArray(getRange, getRangeVector)) {
88 LOGE("parse range failed");
89 return;
90 }
91 if (!ParseJsString(getValue, value)) {
92 value = getRangeVector.front();
93 }
94 if (!ParseJsInteger(getSelected, selected) && !value.empty()) {
95 auto valueIterator = std::find(getRangeVector.begin(), getRangeVector.end(), value);
96 if (valueIterator != getRangeVector.end()) {
97 selected = std::distance(getRangeVector.begin(), valueIterator);
98 }
99 }
100 if (selected < 0 || selected >= getRangeVector.size()) {
101 LOGE("selected is out of range");
102 selected = 0;
103 }
104 }
105
106 auto theme = GetTheme<PickerTheme>();
107 if (!theme) {
108 LOGE("PickerText Theme is null");
109 return;
110 }
111 TextPickerModel::GetInstance()->Create(theme);
112 TextPickerModel::GetInstance()->SetRange(getRangeVector);
113 TextPickerModel::GetInstance()->SetSelected(selected);
114 TextPickerModel::GetInstance()->SetValue(value);
115 JSInteractableView::SetFocusable(false);
116 JSInteractableView::SetFocusNode(true);
117 }
118 }
119
SetDefaultPickerItemHeight(const JSCallbackInfo & info)120 void JSTextPicker::SetDefaultPickerItemHeight(const JSCallbackInfo& info)
121 {
122 if (info.Length() < 1) {
123 LOGE("The arg is wrong, it is supposed to have atleast 1 argument.");
124 return;
125 }
126 Dimension height;
127 if (info[0]->IsNumber() || info[0]->IsString()) {
128 if (!ParseJsDimensionFp(info[0], height)) {
129 return;
130 }
131 }
132 TextPickerModel::GetInstance()->SetDefaultPickerItemHeight(height);
133 }
134
OnAccept(const JSCallbackInfo & info)135 void JSTextPicker::OnAccept(const JSCallbackInfo& info) {}
136
OnCancel(const JSCallbackInfo & info)137 void JSTextPicker::OnCancel(const JSCallbackInfo& info) {}
138
OnChange(const JSCallbackInfo & info)139 void JSTextPicker::OnChange(const JSCallbackInfo& info)
140 {
141 if (!info[0]->IsFunction()) {
142 return;
143 }
144 auto jsFunc = JSRef<JSFunc>::Cast(info[0]);
145 auto onChange = [execCtx = info.GetExecutionContext(), func = std::move(jsFunc)](
146 const std::string& value, double index) {
147 JAVASCRIPT_EXECUTION_SCOPE_WITH_CHECK(execCtx);
148 ACE_SCORING_EVENT("TextPicker.onChange");
149 auto params = ConvertToJSValues(value, index);
150 func->Call(JSRef<JSObject>(), static_cast<int>(params.size()), params.data());
151 };
152 TextPickerModel::GetInstance()->SetOnChange(std::move(onChange));
153 info.ReturnSelf();
154 }
155
JSBind(BindingTarget globalObj)156 void JSTextPickerDialog::JSBind(BindingTarget globalObj)
157 {
158 JSClass<JSTextPickerDialog>::Declare("TextPickerDialog");
159 JSClass<JSTextPickerDialog>::StaticMethod("show", &JSTextPickerDialog::Show);
160
161 JSClass<JSTextPickerDialog>::Bind<>(globalObj);
162 }
163
Show(const JSCallbackInfo & info)164 void JSTextPickerDialog::Show(const JSCallbackInfo& info)
165 {
166 auto scopedDelegate = EngineHelper::GetCurrentDelegate();
167 if (!scopedDelegate) {
168 // this case usually means there is no foreground container, need to figure out the reason.
169 LOGE("scopedDelegate is null, please check");
170 return;
171 }
172 if (info.Length() < 1 || !info[0]->IsObject()) {
173 LOGE("TextPicker create error, info is non-valid");
174 return;
175 }
176
177 auto paramObject = JSRef<JSObject>::Cast(info[0]);
178
179 if (Container::IsCurrentUseNewPipeline()) {
180 auto dialogEvent = DialogEvent(info);
181 auto dialogCancelEvent = DialogCancelEvent(info);
182 TextPickerDialogShow(paramObject, dialogEvent, dialogCancelEvent);
183 return;
184 }
185
186 auto PickerText = AceType::MakeRefPtr<PickerTextComponent>();
187 if (!PickerText) {
188 LOGE("PickerText Component is null");
189 return;
190 }
191 ParseText(PickerText, paramObject);
192 DialogProperties properties {};
193 properties.alignment = DialogAlignment::CENTER;
194 properties.customComponent = PickerText;
195 properties.customStyle = true;
196 AddEvent(PickerText, info);
197 PickerText->SetDialogName("pickerTextDialog");
198 PickerText->OpenDialog(properties);
199 }
200
TextPickerDialogShow(const JSRef<JSObject> & paramObj,const std::map<std::string,NG::DialogTextEvent> & dialogEvent,const std::map<std::string,NG::DialogGestureEvent> & dialogCancelEvent)201 void JSTextPickerDialog::TextPickerDialogShow(const JSRef<JSObject>& paramObj,
202 const std::map<std::string, NG::DialogTextEvent>& dialogEvent,
203 const std::map<std::string, NG::DialogGestureEvent>& dialogCancelEvent)
204 {
205 auto container = Container::Current();
206 if (!container) {
207 return;
208 }
209 auto pipelineContext = AccessibilityManager::DynamicCast<NG::PipelineContext>(container->GetPipelineContext());
210 if (!pipelineContext) {
211 return;
212 }
213
214 auto executor = pipelineContext->GetTaskExecutor();
215 if (!executor) {
216 return;
217 }
218
219 auto getSelected = paramObj->GetProperty("selected");
220 auto defaultHeight = paramObj->GetProperty("defaultPickerItemHeight");
221 JSRef<JSArray> getRange = paramObj->GetProperty("range");
222 std::vector<std::string> getRangeVector;
223 if (!JSViewAbstract::ParseJsStrArray(getRange, getRangeVector)) {
224 LOGE("parse range failed");
225 return;
226 }
227
228 std::string value;
229 uint32_t selected = 0;
230 auto getValue = paramObj->GetProperty("value");
231 if (!JSViewAbstract::ParseJsInteger(getSelected, selected) && JSViewAbstract::ParseJsString(getValue, value)) {
232 auto valueIterator = std::find(getRangeVector.begin(), getRangeVector.end(), value);
233 if (valueIterator != getRangeVector.end()) {
234 selected = std::distance(getRangeVector.begin(), valueIterator);
235 }
236 }
237
238 if (selected < 0 || selected >= getRangeVector.size()) {
239 LOGE("selected is out of range");
240 selected = 0;
241 }
242
243 Dimension height;
244 if (defaultHeight->IsNumber() || defaultHeight->IsString()) {
245 if (!JSViewAbstract::ParseJsDimensionFp(defaultHeight, height)) {
246 return;
247 }
248 }
249
250 auto theme = JSDatePicker::GetTheme<DialogTheme>();
251 if (!theme) {
252 LOGE("DialogTheme is null");
253 return;
254 }
255
256 DialogProperties properties;
257 ButtonInfo buttonInfo;
258 if (SystemProperties::GetDeviceType() == DeviceType::PHONE) {
259 properties.alignment = DialogAlignment::BOTTOM;
260 } else {
261 properties.alignment = DialogAlignment::CENTER;
262 }
263 properties.customStyle = false;
264 properties.offset = DimensionOffset(Offset(0, -theme->GetMarginBottom().ConvertToPx()));
265
266 auto context = AccessibilityManager::DynamicCast<NG::PipelineContext>(pipelineContext);
267 auto overlayManager = context ? context->GetOverlayManager() : nullptr;
268 executor->PostTask(
269 [properties, selected, getRangeVector, dialogEvent, height, dialogCancelEvent,
270 weak = WeakPtr<NG::OverlayManager>(overlayManager)] {
271 auto overlayManager = weak.Upgrade();
272 CHECK_NULL_VOID(overlayManager);
273 overlayManager->ShowTextDialog(
274 properties, selected, height, getRangeVector, dialogEvent, dialogCancelEvent);
275 },
276 TaskExecutor::TaskType::UI);
277 }
278
DialogEvent(const JSCallbackInfo & info)279 std::map<std::string, NG::DialogTextEvent> JSTextPickerDialog::DialogEvent(const JSCallbackInfo& info)
280 {
281 std::map<std::string, NG::DialogTextEvent> dialogEvent;
282 if (info.Length() < 1 || !info[0]->IsObject()) {
283 LOGE("TextPicker AddEvent error, info is non-valid");
284 return dialogEvent;
285 }
286 auto paramObject = JSRef<JSObject>::Cast(info[0]);
287 auto onAccept = paramObject->GetProperty("onAccept");
288 if (!onAccept->IsUndefined() && onAccept->IsFunction()) {
289 auto jsFunc = AceType::MakeRefPtr<JsFunction>(JSRef<JSObject>(), JSRef<JSFunc>::Cast(onAccept));
290 auto acceptId = [execCtx = info.GetExecutionContext(), func = std::move(jsFunc)](const std::string& info) {
291 JAVASCRIPT_EXECUTION_SCOPE_WITH_CHECK(execCtx);
292 std::vector<std::string> keys = { "value", "index" };
293 ACE_SCORING_EVENT("TextPickerDialog.onAccept");
294 func->Execute(keys, info);
295 };
296 dialogEvent["acceptId"] = acceptId;
297 }
298 auto onChange = paramObject->GetProperty("onChange");
299 if (!onChange->IsUndefined() && onChange->IsFunction()) {
300 auto jsFunc = AceType::MakeRefPtr<JsFunction>(JSRef<JSObject>(), JSRef<JSFunc>::Cast(onChange));
301 auto changeId = [execCtx = info.GetExecutionContext(), func = std::move(jsFunc)](const std::string& info) {
302 JAVASCRIPT_EXECUTION_SCOPE_WITH_CHECK(execCtx);
303 std::vector<std::string> keys = { "value", "index" };
304 ACE_SCORING_EVENT("TextPickerDialog.onChange");
305 func->Execute(keys, info);
306 };
307 dialogEvent["changeId"] = changeId;
308 }
309 return dialogEvent;
310 }
311
DialogCancelEvent(const JSCallbackInfo & info)312 std::map<std::string, NG::DialogGestureEvent> JSTextPickerDialog::DialogCancelEvent(const JSCallbackInfo& info)
313 {
314 std::map<std::string, NG::DialogGestureEvent> dialogCancelEvent;
315 if (info.Length() < 1 || !info[0]->IsObject()) {
316 LOGE("TextPicker AddEvent error, info is non-valid");
317 return dialogCancelEvent;
318 }
319 auto paramObject = JSRef<JSObject>::Cast(info[0]);
320 auto onCancel = paramObject->GetProperty("onCancel");
321 if (!onCancel->IsUndefined() && onCancel->IsFunction()) {
322 auto jsFunc = AceType::MakeRefPtr<JsFunction>(JSRef<JSObject>(), JSRef<JSFunc>::Cast(onCancel));
323 auto cancelId = [execCtx = info.GetExecutionContext(), func = std::move(jsFunc)](const GestureEvent& /*info*/) {
324 JAVASCRIPT_EXECUTION_SCOPE_WITH_CHECK(execCtx);
325 ACE_SCORING_EVENT("TextPickerDialog.onCancel");
326 func->Execute();
327 };
328 dialogCancelEvent["cancelId"] = cancelId;
329 }
330 return dialogCancelEvent;
331 }
332
AddEvent(RefPtr<PickerTextComponent> & picker,const JSCallbackInfo & info)333 void JSTextPickerDialog::AddEvent(RefPtr<PickerTextComponent>& picker, const JSCallbackInfo& info)
334 {
335 if (info.Length() < 1 || !info[0]->IsObject()) {
336 LOGE("TextPicker AddEvent error, info is non-valid");
337 return;
338 }
339 auto paramObject = JSRef<JSObject>::Cast(info[0]);
340 auto onAccept = paramObject->GetProperty("onAccept");
341 if (!onAccept->IsUndefined() && onAccept->IsFunction()) {
342 auto jsFunc = AceType::MakeRefPtr<JsFunction>(JSRef<JSObject>(), JSRef<JSFunc>::Cast(onAccept));
343 auto acceptId =
344 EventMarker([execCtx = info.GetExecutionContext(), func = std::move(jsFunc)](const std::string& info) {
345 JAVASCRIPT_EXECUTION_SCOPE_WITH_CHECK(execCtx);
346 std::vector<std::string> keys = { "value", "index" };
347 ACE_SCORING_EVENT("TextPickerDialog.onAccept");
348 func->Execute(keys, info);
349 });
350 picker->SetDialogAcceptEvent(acceptId);
351 }
352 auto onCancel = paramObject->GetProperty("onCancel");
353 if (!onCancel->IsUndefined() && onCancel->IsFunction()) {
354 auto jsFunc = AceType::MakeRefPtr<JsFunction>(JSRef<JSObject>(), JSRef<JSFunc>::Cast(onCancel));
355 auto cancelId = EventMarker([execCtx = info.GetExecutionContext(), func = std::move(jsFunc)]() {
356 JAVASCRIPT_EXECUTION_SCOPE_WITH_CHECK(execCtx);
357 ACE_SCORING_EVENT("TextPickerDialog.onCancel");
358 func->Execute();
359 });
360 picker->SetDialogCancelEvent(cancelId);
361 }
362 auto onChange = paramObject->GetProperty("onChange");
363 if (!onChange->IsUndefined() && onChange->IsFunction()) {
364 auto jsFunc = AceType::MakeRefPtr<JsFunction>(JSRef<JSObject>(), JSRef<JSFunc>::Cast(onChange));
365 auto changeId =
366 EventMarker([execCtx = info.GetExecutionContext(), func = std::move(jsFunc)](const std::string& info) {
367 JAVASCRIPT_EXECUTION_SCOPE_WITH_CHECK(execCtx);
368 std::vector<std::string> keys = { "value", "index" };
369 ACE_SCORING_EVENT("TextPickerDialog.onChange");
370 func->Execute(keys, info);
371 });
372 picker->SetDialogChangeEvent(changeId);
373 }
374 }
375
ParseText(RefPtr<PickerTextComponent> & component,const JSRef<JSObject> & paramObj)376 void JSTextPickerDialog::ParseText(RefPtr<PickerTextComponent>& component, const JSRef<JSObject>& paramObj)
377 {
378 auto getSelected = paramObj->GetProperty("selected");
379 auto defaultHeight = paramObj->GetProperty("defaultPickerItemHeight");
380 JSRef<JSArray> getRange = paramObj->GetProperty("range");
381 std::vector<std::string> getRangeVector;
382 if (!JSViewAbstract::ParseJsStrArray(getRange, getRangeVector)) {
383 LOGE("parse range failed");
384 return;
385 }
386
387 std::string value = "";
388 uint32_t selected = 0;
389 auto getValue = paramObj->GetProperty("value");
390 if (!JSViewAbstract::ParseJsInteger(getSelected, selected) && JSViewAbstract::ParseJsString(getValue, value)) {
391 auto valueIterator = std::find(getRangeVector.begin(), getRangeVector.end(), value);
392 if (valueIterator != getRangeVector.end()) {
393 selected = std::distance(getRangeVector.begin(), valueIterator);
394 }
395 }
396
397 if (selected < 0 || selected >= getRangeVector.size()) {
398 LOGE("selected is out of range");
399 selected = 0;
400 }
401
402 Dimension height;
403 if (defaultHeight->IsNumber() || defaultHeight->IsString()) {
404 if (!JSViewAbstract::ParseJsDimensionFp(defaultHeight, height)) {
405 return;
406 }
407 }
408
409 component->SetIsDialog(true);
410 component->SetIsCreateDialogComponent(true);
411 if (!defaultHeight->IsEmpty()) {
412 component->SetColumnHeight(height);
413 component->SetDefaultHeight(true);
414 }
415 component->SetSelected(selected);
416 component->SetRange(getRangeVector);
417 }
418 } // namespace OHOS::Ace::Framework
419