1 /*
2 * Copyright (c) 2021-2023 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_indexer.h"
17
18 #include "base/geometry/dimension.h"
19 #include "base/log/ace_scoring_log.h"
20 #include "base/utils/utils.h"
21 #include "bridge/declarative_frontend/jsview/js_interactable_view.h"
22 #include "bridge/declarative_frontend/jsview/js_scroller.h"
23 #include "bridge/declarative_frontend/jsview/js_view_common_def.h"
24 #include "bridge/declarative_frontend/jsview/models/indexer_model_impl.h"
25 #include "core/components/common/properties/text_style.h"
26 #include "core/components_ng/pattern/indexer/indexer_model_ng.h"
27
28 namespace OHOS::Ace {
29 std::unique_ptr<IndexerModel> IndexerModel::instance_ = nullptr;
30 std::mutex IndexerModel::mutex_;
GetInstance()31 IndexerModel* IndexerModel::GetInstance()
32 {
33 if (!instance_) {
34 std::lock_guard<std::mutex> lock(mutex_);
35 if (!instance_) {
36 #ifdef NG_BUILD
37 instance_.reset(new NG::IndexerModelNG());
38 #else
39 if (Container::IsCurrentUseNewPipeline()) {
40 instance_.reset(new NG::IndexerModelNG());
41 } else {
42 instance_.reset(new Framework::IndexerModelImpl());
43 }
44 #endif
45 }
46 }
47 return instance_.get();
48 }
49 } // namespace OHOS::Ace
50
51 namespace OHOS::Ace::Framework {
52 namespace {
53 const std::vector<FontStyle> FONT_STYLES = { FontStyle::NORMAL, FontStyle::ITALIC };
54 const std::vector<V2::AlignStyle> ALIGN_STYLE = { V2::AlignStyle::LEFT, V2::AlignStyle::RIGHT };
55 const std::vector<NG::AlignStyle> NG_ALIGN_STYLE = { NG::AlignStyle::LEFT, NG::AlignStyle::RIGHT };
56 constexpr Dimension DEFAULT_ITEM_SIZE = 24.0_vp;
57 }; // namespace
58
ParseIndexerSelectedObject(const JSCallbackInfo & info,const JSRef<JSVal> & changeEventVal,bool isMethodProp=false)59 void JSIndexer::ParseIndexerSelectedObject(
60 const JSCallbackInfo& info, const JSRef<JSVal>& changeEventVal, bool isMethodProp = false)
61 {
62 CHECK_NULL_VOID(changeEventVal->IsFunction());
63 auto jsFunc = AceType::MakeRefPtr<JsFunction>(JSRef<JSObject>(), JSRef<JSFunc>::Cast(changeEventVal));
64 auto changeEvent = [execCtx = info.GetExecutionContext(), func = std::move(jsFunc)](const int32_t selected) {
65 JAVASCRIPT_EXECUTION_SCOPE_WITH_CHECK(execCtx);
66 ACE_SCORING_EVENT("Indexer.SelectedChangeEvent");
67 auto newJSVal = JSRef<JSVal>::Make(ToJSValue(selected));
68 func->ExecuteJS(1, &newJSVal);
69 };
70
71 if (isMethodProp) {
72 IndexerModel::GetInstance()->SetChangeEvent(changeEvent);
73 } else {
74 IndexerModel::GetInstance()->SetCreatChangeEvent(changeEvent);
75 }
76 }
77
Create(const JSCallbackInfo & args)78 void JSIndexer::Create(const JSCallbackInfo& args)
79 {
80 if (args.Length() >= 1 && args[0]->IsObject()) {
81 size_t length = 0;
82 int32_t selectedVal = 0;
83 auto param = JsonUtil::ParseJsonString(args[0]->ToString());
84 std::unique_ptr<JsonValue> arrayVal;
85 if (param && !param->IsNull()) {
86 arrayVal = param->GetValue("arrayValue");
87 if (arrayVal && arrayVal->IsArray()) {
88 length = static_cast<uint32_t>(arrayVal->GetArraySize());
89 }
90 }
91 std::vector<std::string> indexerArray;
92 for (size_t i = 0; i < length; i++) {
93 auto value = arrayVal->GetArrayItem(i);
94 if (value) {
95 indexerArray.emplace_back(value->GetString());
96 }
97 }
98
99 JSRef<JSObject> paramObj = JSRef<JSObject>::Cast(args[0]);
100 JSRef<JSVal> selectedProperty = paramObj->GetProperty("selected");
101 if (selectedProperty->IsNumber()) {
102 selectedVal = selectedProperty->ToNumber<int32_t>();
103 }
104 IndexerModel::GetInstance()->Create(indexerArray, selectedVal);
105 if (length <= 0 || !selectedProperty->IsObject()) {
106 return;
107 }
108 JSRef<JSObject> selectedObj = JSRef<JSObject>::Cast(selectedProperty);
109 auto selectedValueProperty = selectedObj->GetProperty("value");
110 if (selectedValueProperty->IsNumber()) {
111 selectedVal = selectedValueProperty->ToNumber<int32_t>();
112 }
113 JSRef<JSVal> changeEventVal = selectedObj->GetProperty("changeEvent");
114 if (!changeEventVal.IsEmpty()) {
115 if (!changeEventVal->IsUndefined() && changeEventVal->IsFunction()) {
116 ParseIndexerSelectedObject(args, changeEventVal);
117 }
118 return;
119 }
120
121 args.ReturnSelf();
122 }
123 }
124
SetSelectedColor(const JSCallbackInfo & args)125 void JSIndexer::SetSelectedColor(const JSCallbackInfo& args)
126 {
127 if (args.Length() < 1) {
128 LOGE("The argv is wrong, it is supposed to have at least 1 argument");
129 return;
130 }
131 IndexerModel::GetInstance()->SetSelectedColor(PaseColor(args));
132 }
133
SetColor(const JSCallbackInfo & args)134 void JSIndexer::SetColor(const JSCallbackInfo& args)
135 {
136 if (args.Length() < 1) {
137 LOGE("The argv is wrong, it is supposed to have at least 1 argument");
138 return;
139 }
140 IndexerModel::GetInstance()->SetColor(PaseColor(args));
141 }
142
SetPopupColor(const JSCallbackInfo & args)143 void JSIndexer::SetPopupColor(const JSCallbackInfo& args)
144 {
145 if (args.Length() < 1) {
146 LOGE("The argv is wrong, it is supposed to have at least 1 argument");
147 return;
148 }
149 IndexerModel::GetInstance()->SetPopupColor(PaseColor(args));
150 }
151
SetSelectedBackgroundColor(const JSCallbackInfo & args)152 void JSIndexer::SetSelectedBackgroundColor(const JSCallbackInfo& args)
153 {
154 if (args.Length() < 1) {
155 LOGW("The argv is wrong, it is supposed to have at least 1 argument");
156 return;
157 }
158 IndexerModel::GetInstance()->SetSelectedBackgroundColor(PaseColor(args));
159 }
160
SetPopupBackground(const JSCallbackInfo & args)161 void JSIndexer::SetPopupBackground(const JSCallbackInfo& args)
162 {
163 if (args.Length() < 1) {
164 LOGW("The argv is wrong, it is supposed to have at least 1 argument");
165 return;
166 }
167 IndexerModel::GetInstance()->SetPopupBackground(PaseColor(args));
168 }
169
SetUsingPopup(bool state)170 void JSIndexer::SetUsingPopup(bool state)
171 {
172 IndexerModel::GetInstance()->SetUsingPopup(state);
173 }
174
SetSelectedFont(const JSCallbackInfo & args)175 void JSIndexer::SetSelectedFont(const JSCallbackInfo& args)
176 {
177 if (args.Length() < 1) {
178 LOGW("The argv is wrong, it is supposed to have at least 1 argument");
179 return;
180 }
181 std::optional<Dimension> fontSize;
182 std::optional<FontWeight> fontWeight;
183 std::optional<std::vector<std::string>> fontFamily;
184 std::optional<FontStyle> fontStyle;
185 if (args[0]->IsObject()) {
186 GetFontContent(args, fontSize, fontWeight, fontFamily, fontStyle);
187 }
188 IndexerModel::GetInstance()->SetSelectedFont(fontSize, fontWeight, fontFamily, fontStyle);
189 }
190
SetPopupFont(const JSCallbackInfo & args)191 void JSIndexer::SetPopupFont(const JSCallbackInfo& args)
192 {
193 if (args.Length() < 1) {
194 LOGW("The argv is wrong, it is supposed to have at least 1 argument");
195 return;
196 }
197 std::optional<Dimension> fontSize;
198 std::optional<FontWeight> fontWeight;
199 std::optional<std::vector<std::string>> fontFamily;
200 std::optional<FontStyle> fontStyle;
201 if (args[0]->IsObject()) {
202 GetFontContent(args, fontSize, fontWeight, fontFamily, fontStyle);
203 }
204 IndexerModel::GetInstance()->SetPopupFont(fontSize, fontWeight, fontFamily, fontStyle);
205 }
206
SetFont(const JSCallbackInfo & args)207 void JSIndexer::SetFont(const JSCallbackInfo& args)
208 {
209 if (args.Length() < 1) {
210 LOGW("The argv is wrong, it is supposed to have at least 1 argument");
211 return;
212 }
213 std::optional<Dimension> fontSize;
214 std::optional<FontWeight> fontWeight;
215 std::optional<std::vector<std::string>> fontFamily;
216 std::optional<FontStyle> fontStyle;
217 if (args[0]->IsObject()) {
218 GetFontContent(args, fontSize, fontWeight, fontFamily, fontStyle);
219 }
220 IndexerModel::GetInstance()->SetFont(fontSize, fontWeight, fontFamily, fontStyle);
221 }
222
JsOnSelected(const JSCallbackInfo & args)223 void JSIndexer::JsOnSelected(const JSCallbackInfo& args)
224 {
225 if (args[0]->IsFunction()) {
226 auto onSelected = [execCtx = args.GetExecutionContext(), func = JSRef<JSFunc>::Cast(args[0])](
227 const int32_t selected) {
228 JAVASCRIPT_EXECUTION_SCOPE(execCtx);
229 auto params = ConvertToJSValues(selected);
230 func->Call(JSRef<JSObject>(), params.size(), params.data());
231 };
232 IndexerModel::GetInstance()->SetOnSelected(onSelected);
233 }
234 }
235
JsOnRequestPopupData(const JSCallbackInfo & args)236 void JSIndexer::JsOnRequestPopupData(const JSCallbackInfo& args)
237 {
238 if (args[0]->IsFunction()) {
239 auto requestPopupData = [execCtx = args.GetExecutionContext(), func = JSRef<JSFunc>::Cast(args[0])](
240 const int32_t selected) {
241 std::vector<std::string> popupData;
242 JAVASCRIPT_EXECUTION_SCOPE_WITH_CHECK(execCtx, popupData);
243 auto params = ConvertToJSValues(selected);
244 JSRef<JSArray> result = func->Call(JSRef<JSObject>(), params.size(), params.data());
245 if (result.IsEmpty()) {
246 LOGE("Error calling onRequestPopupData result is empty.");
247 return popupData;
248 }
249
250 if (!result->IsArray()) {
251 LOGE("Error calling onRequestPopupData result is not array.");
252 return popupData;
253 }
254
255 for (size_t i = 0; i < result->Length(); i++) {
256 if (result->GetValueAt(i)->IsString()) {
257 auto item = result->GetValueAt(i);
258 popupData.emplace_back(item->ToString());
259 } else {
260 LOGE("Error calling onRequestPopupData index %{public}zu is not string.", i);
261 }
262 }
263 return popupData;
264 };
265 IndexerModel::GetInstance()->SetOnRequestPopupData(requestPopupData);
266 }
267 }
268
JsOnPopupSelected(const JSCallbackInfo & args)269 void JSIndexer::JsOnPopupSelected(const JSCallbackInfo& args)
270 {
271 if (args[0]->IsFunction()) {
272 auto onPopupSelected = [execCtx = args.GetExecutionContext(), func = JSRef<JSFunc>::Cast(args[0])](
273 const int32_t selected) {
274 JAVASCRIPT_EXECUTION_SCOPE_WITH_CHECK(execCtx);
275 auto params = ConvertToJSValues(selected);
276 func->Call(JSRef<JSObject>(), params.size(), params.data());
277 };
278 IndexerModel::GetInstance()->SetOnPopupSelected(onPopupSelected);
279 }
280 }
281
GetFontContent(const JSCallbackInfo & args,std::optional<Dimension> & fontSize,std::optional<FontWeight> & fontWeight,std::optional<std::vector<std::string>> & fontFamily,std::optional<FontStyle> & fontStyle)282 void JSIndexer::GetFontContent(const JSCallbackInfo& args, std::optional<Dimension>& fontSize,
283 std::optional<FontWeight>& fontWeight, std::optional<std::vector<std::string>>& fontFamily,
284 std::optional<FontStyle>& fontStyle)
285 {
286 JSRef<JSObject> obj = JSRef<JSObject>::Cast(args[0]);
287 JSRef<JSVal> size = obj->GetProperty("size");
288 CalcDimension fontSizeData;
289 if (ParseJsDimensionFp(size, fontSizeData) && !fontSizeData.IsNegative() &&
290 fontSizeData.Unit() != DimensionUnit::PERCENT) {
291 fontSize = fontSizeData;
292 }
293
294 JSRef<JSVal> weight = obj->GetProperty("weight");
295 if (weight->IsString() || weight->IsNumber()) {
296 fontWeight = ConvertStrToFontWeight(weight->ToString());
297 }
298
299 JSRef<JSVal> family = obj->GetProperty("family");
300 std::vector<std::string> fontFamilies;
301 if (ParseJsFontFamilies(family, fontFamilies)) {
302 fontFamily = fontFamilies;
303 }
304
305 JSRef<JSVal> style = obj->GetProperty("style");
306 if (style->IsNumber()) {
307 int32_t value = style->ToNumber<int32_t>();
308 if (value >= 0 && value < static_cast<int32_t>(FONT_STYLES.size())) {
309 fontStyle = FONT_STYLES[value];
310 }
311 }
312 }
313
SetItemSize(const JSCallbackInfo & args)314 void JSIndexer::SetItemSize(const JSCallbackInfo& args)
315 {
316 if (args.Length() < 1) {
317 LOGW("The argv is wrong, it is supposed to have at least 1 argument");
318 return;
319 }
320 CalcDimension itemSize;
321 if (ParseJsDimensionVp(args[0], itemSize) && GreatNotEqual(itemSize.Value(), 0.0) &&
322 itemSize.Unit() != DimensionUnit::PERCENT) {
323 IndexerModel::GetInstance()->SetItemSize(itemSize);
324 return;
325 }
326 IndexerModel::GetInstance()->SetItemSize(DEFAULT_ITEM_SIZE);
327 }
328
SetAlignStyle(const JSCallbackInfo & args)329 void JSIndexer::SetAlignStyle(const JSCallbackInfo& args)
330 {
331 if (args.Length() < 1) {
332 LOGW("The argv is wrong, it is supposed to have at least 1 argument");
333 return;
334 }
335 int32_t value = Container::IsCurrentUseNewPipeline() ? static_cast<int32_t>(NG::AlignStyle::RIGHT)
336 : static_cast<int32_t>(V2::AlignStyle::RIGHT);
337 auto alignValue = -1;
338 if (args[0]->IsNumber()) {
339 alignValue = args[0]->ToNumber<int32_t>();
340 }
341 if (alignValue >= 0 && alignValue < static_cast<int32_t>(ALIGN_STYLE.size())) {
342 value = alignValue;
343 }
344 IndexerModel::GetInstance()->SetAlignStyle(value);
345 CalcDimension popupHorizontalSpace(-1.0);
346 if (args.Length() > 1) {
347 ParseJsDimensionVp(args[1], popupHorizontalSpace);
348 }
349 IndexerModel::GetInstance()->SetPopupHorizontalSpace(popupHorizontalSpace);
350 }
351
SetSelected(const JSCallbackInfo & args)352 void JSIndexer::SetSelected(const JSCallbackInfo& args)
353 {
354 if (args.Length() >= 1) {
355 int32_t selected = 0;
356 if (ParseJsInteger<int32_t>(args[0], selected)) {
357 IndexerModel::GetInstance()->SetSelected(selected);
358 }
359 if (args.Length() > 1 && args[1]->IsFunction()) {
360 ParseIndexerSelectedObject(args, args[1], true);
361 }
362 }
363 }
364
SetPopupPosition(const JSCallbackInfo & args)365 void JSIndexer::SetPopupPosition(const JSCallbackInfo& args)
366 {
367 if (args[0]->IsObject()) {
368 JSRef<JSObject> obj = JSRef<JSObject>::Cast(args[0]);
369 std::optional<Dimension> xOpt;
370 std::optional<Dimension> yOpt;
371 CalcDimension x;
372 CalcDimension y;
373 JSRef<JSVal> xVal = obj->GetProperty("x");
374 JSRef<JSVal> yVal = obj->GetProperty("y");
375 if (JSViewAbstract::ParseJsDimensionVp(xVal, x)) {
376 xOpt = x;
377 }
378 IndexerModel::GetInstance()->SetPopupPositionX(xOpt);
379 if (JSViewAbstract::ParseJsDimensionVp(yVal, y)) {
380 yOpt = y;
381 }
382 IndexerModel::GetInstance()->SetPopupPositionY(yOpt);
383 }
384 }
385
SetPopupSelectedColor(const JSCallbackInfo & args)386 void JSIndexer::SetPopupSelectedColor(const JSCallbackInfo& args)
387 {
388 if (args.Length() < 1) {
389 LOGW("The argv is wrong, it is supposed to have at least 1 argument");
390 return;
391 }
392 IndexerModel::GetInstance()->SetPopupSelectedColor(PaseColor(args));
393 CalcDimension popupHorizontalSpace(-1.0);
394 if (args.Length() > 1) {
395 ParseJsDimensionVp(args[1], popupHorizontalSpace);
396 }
397 IndexerModel::GetInstance()->SetPopupHorizontalSpace(popupHorizontalSpace);
398 }
399
SetPopupUnselectedColor(const JSCallbackInfo & args)400 void JSIndexer::SetPopupUnselectedColor(const JSCallbackInfo& args)
401 {
402 if (args.Length() < 1) {
403 LOGW("The argv is wrong, it is supposed to have at least 1 argument");
404 return;
405 }
406 IndexerModel::GetInstance()->SetPopupUnselectedColor(PaseColor(args));
407 }
408
SetPopupItemFont(const JSCallbackInfo & args)409 void JSIndexer::SetPopupItemFont(const JSCallbackInfo& args)
410 {
411 CalcDimension fontSize;
412 std::string weight;
413 if (args[0]->IsObject()) {
414 JSRef<JSObject> obj = JSRef<JSObject>::Cast(args[0]);
415 JSRef<JSVal> size = obj->GetProperty("size");
416 if (!size->IsNull()) {
417 CalcDimension fontSizeData;
418 if (ParseJsDimensionFp(size, fontSizeData) && !fontSizeData.IsNegative() &&
419 fontSizeData.Unit() != DimensionUnit::PERCENT) {
420 fontSize = fontSizeData;
421 }
422 }
423
424 auto jsWeight = obj->GetProperty("weight");
425 if (!jsWeight->IsNull()) {
426 if (jsWeight->IsNumber()) {
427 weight = std::to_string(jsWeight->ToNumber<int32_t>());
428 } else {
429 ParseJsString(jsWeight, weight);
430 }
431 }
432 }
433 IndexerModel::GetInstance()->SetFontSize(fontSize);
434 IndexerModel::GetInstance()->SetFontWeight(ConvertStrToFontWeight(weight, FontWeight::MEDIUM));
435 }
436
SetPopupItemBackgroundColor(const JSCallbackInfo & args)437 void JSIndexer::SetPopupItemBackgroundColor(const JSCallbackInfo& args)
438 {
439 if (args.Length() < 1) {
440 LOGW("The argv is wrong, it is supposed to have at least 1 argument");
441 return;
442 }
443 IndexerModel::GetInstance()->SetPopupItemBackground(PaseColor(args));
444 }
445
PaseColor(const JSCallbackInfo & args)446 std::optional<Color> JSIndexer::PaseColor(const JSCallbackInfo& args)
447 {
448 std::optional<Color> colorOpt;
449 Color color;
450 if (ParseJsColor(args[0], color)) {
451 colorOpt = color;
452 }
453 return colorOpt;
454 }
455
JSBind(BindingTarget globalObj)456 void JSIndexer::JSBind(BindingTarget globalObj)
457 {
458 MethodOptions opt = MethodOptions::NONE;
459 JSClass<JSIndexer>::Declare("AlphabetIndexer");
460 JSClass<JSIndexer>::StaticMethod("create", &JSIndexer::Create);
461 // API7 onSelected deprecated
462 JSClass<JSIndexer>::StaticMethod("onSelected", &JSIndexer::JsOnSelected);
463 JSClass<JSIndexer>::StaticMethod("onSelect", &JSIndexer::JsOnSelected);
464 JSClass<JSIndexer>::StaticMethod("color", &JSIndexer::SetColor, opt);
465 JSClass<JSIndexer>::StaticMethod("selectedColor", &JSIndexer::SetSelectedColor, opt);
466 JSClass<JSIndexer>::StaticMethod("popupColor", &JSIndexer::SetPopupColor, opt);
467 JSClass<JSIndexer>::StaticMethod("selectedBackgroundColor", &JSIndexer::SetSelectedBackgroundColor, opt);
468 JSClass<JSIndexer>::StaticMethod("popupBackground", &JSIndexer::SetPopupBackground, opt);
469 JSClass<JSIndexer>::StaticMethod("usingPopup", &JSIndexer::SetUsingPopup, opt);
470 JSClass<JSIndexer>::StaticMethod("selectedFont", &JSIndexer::SetSelectedFont);
471 JSClass<JSIndexer>::StaticMethod("font", &JSIndexer::SetFont);
472 JSClass<JSIndexer>::StaticMethod("popupFont", &JSIndexer::SetPopupFont);
473 JSClass<JSIndexer>::StaticMethod("itemSize", &JSIndexer::SetItemSize, opt);
474 JSClass<JSIndexer>::StaticMethod("alignStyle", &JSIndexer::SetAlignStyle, opt);
475 JSClass<JSIndexer>::StaticMethod("onRequestPopupData", &JSIndexer::JsOnRequestPopupData, opt);
476 JSClass<JSIndexer>::StaticMethod("selected", &JSIndexer::SetSelected, opt);
477 JSClass<JSIndexer>::StaticMethod("popupPosition", &JSIndexer::SetPopupPosition, opt);
478 JSClass<JSIndexer>::StaticMethod("popupSelectedColor", &JSIndexer::SetPopupSelectedColor, opt);
479 JSClass<JSIndexer>::StaticMethod("popupUnselectedColor", &JSIndexer::SetPopupUnselectedColor, opt);
480 JSClass<JSIndexer>::StaticMethod("popupItemFont", &JSIndexer::SetPopupItemFont);
481 JSClass<JSIndexer>::StaticMethod("popupItemBackgroundColor", &JSIndexer::SetPopupItemBackgroundColor, opt);
482 // keep compatible, need remove after
483 JSClass<JSIndexer>::StaticMethod("onPopupSelected", &JSIndexer::JsOnPopupSelected, opt);
484 JSClass<JSIndexer>::StaticMethod("onPopupSelect", &JSIndexer::JsOnPopupSelected, opt);
485 JSClass<JSIndexer>::StaticMethod("onAppear", &JSInteractableView::JsOnAppear);
486 JSClass<JSIndexer>::StaticMethod("onDisAppear", &JSInteractableView::JsOnDisAppear);
487 JSClass<JSIndexer>::StaticMethod("onTouch", &JSInteractableView::JsOnTouch);
488 JSClass<JSIndexer>::InheritAndBind<JSViewAbstract>(globalObj);
489 }
490 } // namespace OHOS::Ace::Framework
491