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_list_item.h"
17
18 #include <cstdint>
19 #include <functional>
20
21 #include "base/log/ace_scoring_log.h"
22 #include "bridge/declarative_frontend/engine/functions/js_drag_function.h"
23 #include "bridge/declarative_frontend/engine/functions/js_function.h"
24 #include "bridge/declarative_frontend/jsview/js_utils.h"
25 #include "bridge/declarative_frontend/jsview/js_view_common_def.h"
26 #include "bridge/declarative_frontend/jsview/models/list_item_model_impl.h"
27 #include "core/common/container.h"
28 #include "core/components_ng/base/view_abstract_model.h"
29 #include "core/components_ng/base/view_stack_processor.h"
30 #include "core/components_ng/event/gesture_event_hub.h"
31 #include "core/components_ng/pattern/list/list_item_model.h"
32 #include "core/components_ng/pattern/list/list_item_model_ng.h"
33
34 namespace OHOS::Ace {
35
36 std::unique_ptr<ListItemModel> ListItemModel::instance_ = nullptr;
37 std::mutex ListItemModel::mutex_;
38
GetInstance()39 ListItemModel* ListItemModel::GetInstance()
40 {
41 if (!instance_) {
42 std::lock_guard<std::mutex> lock(mutex_);
43 if (!instance_) {
44 #ifdef NG_BUILD
45 instance_.reset(new NG::ListItemModelNG());
46 #else
47 if (Container::IsCurrentUseNewPipeline()) {
48 instance_.reset(new NG::ListItemModelNG());
49 } else {
50 instance_.reset(new Framework::ListItemModelImpl());
51 }
52 #endif
53 }
54 }
55 return instance_.get();
56 }
57
58 } // namespace OHOS::Ace
59
60 namespace OHOS::Ace::Framework {
61
Create(const JSCallbackInfo & args)62 void JSListItem::Create(const JSCallbackInfo& args)
63 {
64 if (Container::IsCurrentUsePartialUpdate()) {
65 CreateForPartialUpdate(args);
66 return;
67 }
68 std::string type;
69 if (args.Length() >= 1 && args[0]->IsString()) {
70 type = args[0]->ToString();
71 }
72
73 ListItemModel::GetInstance()->Create();
74 if (!type.empty()) {
75 ListItemModel::GetInstance()->SetType(type);
76 }
77 args.ReturnSelf();
78 }
79
Pop()80 void JSListItem::Pop()
81 {
82 JSContainerBase::Pop();
83 ListItemModel::GetInstance()->OnDidPop();
84 }
85
CreateForPartialUpdate(const JSCallbackInfo & args)86 void JSListItem::CreateForPartialUpdate(const JSCallbackInfo& args)
87 {
88 const int32_t ARGS_LENGTH = 2;
89 auto len = args.Length();
90 if (len < ARGS_LENGTH) {
91 ListItemModel::GetInstance()->Create();
92 return;
93 }
94 JSRef<JSVal> arg0 = args[0];
95 if (!arg0->IsFunction()) {
96 ListItemModel::GetInstance()->Create();
97 return;
98 }
99
100 JSRef<JSVal> arg1 = args[1];
101 if (!arg1->IsBoolean()) {
102 return;
103 }
104 const bool isLazy = arg1->ToBoolean();
105
106 V2::ListItemStyle listItemStyle = V2::ListItemStyle::NONE;
107 if (len > ARGS_LENGTH && args[ARGS_LENGTH]->IsObject()) {
108 JSRef<JSObject> obj = JSRef<JSObject>::Cast(args[ARGS_LENGTH]);
109 JSRef<JSVal> styleObj = obj->GetProperty("style");
110 listItemStyle = styleObj->IsNumber() ? static_cast<V2::ListItemStyle>(styleObj->ToNumber<int32_t>())
111 : V2::ListItemStyle::NONE;
112 }
113
114 if (!isLazy) {
115 if (Container::GreatOrEqualAPIVersion(PlatformVersion::VERSION_TWELVE)) {
116 ListItemModel::GetInstance()->Create(nullptr, listItemStyle);
117 } else {
118 ListItemModel::GetInstance()->Create();
119 }
120 } else {
121 RefPtr<JsFunction> jsDeepRender = AceType::MakeRefPtr<JsFunction>(args.This(), JSRef<JSFunc>::Cast(arg0));
122 auto listItemDeepRenderFunc = [execCtx = args.GetExecutionContext(),
123 jsDeepRenderFunc = std::move(jsDeepRender)](int32_t nodeId) {
124 ACE_SCOPED_TRACE("JSListItem::ExecuteDeepRender");
125 JAVASCRIPT_EXECUTION_SCOPE(execCtx);
126 JSRef<JSVal> jsParams[2];
127 jsParams[0] = JSRef<JSVal>::Make(ToJSValue(nodeId));
128 jsParams[1] = JSRef<JSVal>::Make(ToJSValue(true));
129 jsDeepRenderFunc->ExecuteJS(2, jsParams);
130 }; // listItemDeepRenderFunc lambda
131 ListItemModel::GetInstance()->Create(std::move(listItemDeepRenderFunc), listItemStyle);
132 ListItemModel::GetInstance()->SetIsLazyCreating(isLazy);
133 }
134 }
135
SetSticky(int32_t sticky)136 void JSListItem::SetSticky(int32_t sticky)
137 {
138 ListItemModel::GetInstance()->SetSticky(static_cast<V2::StickyMode>(sticky));
139 }
140
SetEditable(const JSCallbackInfo & args)141 void JSListItem::SetEditable(const JSCallbackInfo& args)
142 {
143 if (args[0]->IsBoolean()) {
144 uint32_t value = args[0]->ToBoolean() ? V2::EditMode::DELETABLE | V2::EditMode::MOVABLE : V2::EditMode::SHAM;
145 ListItemModel::GetInstance()->SetEditMode(value);
146 return;
147 }
148
149 if (args[0]->IsNumber()) {
150 auto value = args[0]->ToNumber<uint32_t>();
151 ListItemModel::GetInstance()->SetEditMode(value);
152 return;
153 }
154 }
155
SetSelectable(const JSCallbackInfo & info)156 void JSListItem::SetSelectable(const JSCallbackInfo& info)
157 {
158 if (info.Length() < 1) {
159 return;
160 }
161 bool selectable = true;
162 if (info[0]->IsBoolean()) {
163 selectable = info[0]->ToBoolean();
164 }
165 ListItemModel::GetInstance()->SetSelectable(selectable);
166 }
167
SetSelected(const JSCallbackInfo & info)168 void JSListItem::SetSelected(const JSCallbackInfo& info)
169 {
170 if (info.Length() < 1) {
171 return;
172 }
173 bool select = false;
174 if (info[0]->IsBoolean()) {
175 select = info[0]->ToBoolean();
176 }
177 ListItemModel::GetInstance()->SetSelected(select);
178
179 if (info.Length() > 1 && info[1]->IsFunction()) {
180 auto jsFunc = AceType::MakeRefPtr<JsFunction>(JSRef<JSObject>(), JSRef<JSFunc>::Cast(info[1]));
181 auto targetNode = AceType::WeakClaim(NG::ViewStackProcessor::GetInstance()->GetMainFrameNode());
182 auto changeEvent = [execCtx = info.GetExecutionContext(), func = std::move(jsFunc), node = targetNode](
183 bool param) {
184 JAVASCRIPT_EXECUTION_SCOPE_WITH_CHECK(execCtx);
185 ACE_SCORING_EVENT("ListItem.ChangeEvent");
186 auto newJSVal = JSRef<JSVal>::Make(ToJSValue(param));
187 PipelineContext::SetCallBackNode(node);
188 func->ExecuteJS(1, &newJSVal);
189 };
190 ListItemModel::GetInstance()->SetSelectChangeEvent(std::move(changeEvent));
191 }
192 }
193
JsParseDeleteArea(const JsiExecutionContext & context,const JSRef<JSVal> & jsValue,bool isStartArea)194 void JSListItem::JsParseDeleteArea(const JsiExecutionContext& context, const JSRef<JSVal>& jsValue, bool isStartArea)
195 {
196 auto deleteAreaObj = JSRef<JSObject>::Cast(jsValue);
197
198 std::function<void()> builderAction;
199 auto builderObject = deleteAreaObj->GetProperty("builder");
200 if (builderObject->IsFunction()) {
201 auto builderFunc = AceType::MakeRefPtr<JsFunction>(JSRef<JSFunc>::Cast(builderObject));
202 builderAction = [builderFunc]() { builderFunc->Execute(); };
203 }
204
205 auto onAction = deleteAreaObj->GetProperty("onAction");
206 std::function<void()> onActionCallback;
207 if (onAction->IsFunction()) {
208 onActionCallback = [execCtx = context, func = JSRef<JSFunc>::Cast(onAction)]() {
209 func->Call(JSRef<JSObject>());
210 return;
211 };
212 }
213 auto onEnterActionArea = deleteAreaObj->GetProperty("onEnterActionArea");
214 std::function<void()> onEnterActionAreaCallback;
215 if (onEnterActionArea->IsFunction()) {
216 onEnterActionAreaCallback = [execCtx = context,
217 func = JSRef<JSFunc>::Cast(onEnterActionArea)]() {
218 func->Call(JSRef<JSObject>());
219 return;
220 };
221 }
222 auto onExitActionArea = deleteAreaObj->GetProperty("onExitActionArea");
223 std::function<void()> onExitActionAreaCallback;
224 if (onExitActionArea->IsFunction()) {
225 onExitActionAreaCallback = [execCtx = context,
226 func = JSRef<JSFunc>::Cast(onExitActionArea)]() {
227 func->Call(JSRef<JSObject>());
228 return;
229 };
230 }
231 auto actionAreaDistance = deleteAreaObj->GetProperty("actionAreaDistance");
232 CalcDimension length;
233 if (!ParseJsDimensionVp(actionAreaDistance, length)) {
234 auto listItemTheme = GetTheme<ListItemTheme>();
235 length = listItemTheme->GetDeleteDistance();
236 }
237 auto onStateChange = deleteAreaObj->GetProperty("onStateChange");
238 std::function<void(SwipeActionState state)> onStateChangeCallback;
239 if (onStateChange->IsFunction()) {
240 onStateChangeCallback = [execCtx = context,
241 func = JSRef<JSFunc>::Cast(onStateChange)](SwipeActionState state) {
242 JAVASCRIPT_EXECUTION_SCOPE_WITH_CHECK(execCtx);
243 auto params = ConvertToJSValues(state);
244 func->Call(JSRef<JSObject>(), params.size(), params.data());
245 return;
246 };
247 }
248
249 ListItemModel::GetInstance()->SetDeleteArea(std::move(builderAction), std::move(onActionCallback),
250 std::move(onEnterActionAreaCallback), std::move(onExitActionAreaCallback), std::move(onStateChangeCallback),
251 length, isStartArea);
252 }
253
SetSwiperAction(const JSCallbackInfo & args)254 void JSListItem::SetSwiperAction(const JSCallbackInfo& args)
255 {
256 if (!args[0]->IsObject()) {
257 return;
258 }
259 JSRef<JSObject> obj = JSRef<JSObject>::Cast(args[0]);
260 ParseSwiperAction(obj, args.GetExecutionContext());
261 }
262
ParseSwiperAction(const JSRef<JSObject> & obj,const JsiExecutionContext & context)263 void JSListItem::ParseSwiperAction(const JSRef<JSObject>& obj, const JsiExecutionContext& context)
264 {
265 std::function<void()> startAction;
266 auto startObject = obj->GetProperty("start");
267 if (startObject->IsObject()) {
268 if (startObject->IsFunction()) {
269 auto builderFunc = AceType::MakeRefPtr<JsFunction>(JSRef<JSFunc>::Cast(startObject));
270 startAction = [builderFunc]() { builderFunc->Execute(); };
271 ListItemModel::GetInstance()->SetDeleteArea(
272 std::move(startAction), nullptr, nullptr, nullptr, nullptr, Dimension(0, DimensionUnit::VP), true);
273 } else {
274 JsParseDeleteArea(context, startObject, true);
275 }
276 } else {
277 ListItemModel::GetInstance()->SetDeleteArea(
278 nullptr, nullptr, nullptr, nullptr, nullptr, Dimension(0, DimensionUnit::VP), true);
279 }
280
281 std::function<void()> endAction;
282 auto endObject = obj->GetProperty("end");
283 if (endObject->IsObject()) {
284 if (endObject->IsFunction()) {
285 auto builderFunc = AceType::MakeRefPtr<JsFunction>(JSRef<JSFunc>::Cast(endObject));
286 endAction = [builderFunc]() { builderFunc->Execute(); };
287 ListItemModel::GetInstance()->SetDeleteArea(
288 std::move(endAction), nullptr, nullptr, nullptr, nullptr, Dimension(0, DimensionUnit::VP), false);
289 } else {
290 JsParseDeleteArea(context, endObject, false);
291 }
292 } else {
293 ListItemModel::GetInstance()->SetDeleteArea(
294 nullptr, nullptr, nullptr, nullptr, nullptr, Dimension(0, DimensionUnit::VP), false);
295 }
296
297 auto edgeEffect = obj->GetProperty("edgeEffect");
298 V2::SwipeEdgeEffect swipeEdgeEffect = V2::SwipeEdgeEffect::Spring;
299 if (edgeEffect->IsNumber()) {
300 swipeEdgeEffect = static_cast<V2::SwipeEdgeEffect>(edgeEffect->ToNumber<int32_t>());
301 }
302
303 auto onOffsetChangeFunc = obj->GetProperty("onOffsetChange");
304 std::function<void(int32_t offset)> onOffsetChangeCallback;
305 if (onOffsetChangeFunc->IsFunction()) {
306 onOffsetChangeCallback = [execCtx = context,
307 func = JSRef<JSFunc>::Cast(onOffsetChangeFunc)](int32_t offset) {
308 JAVASCRIPT_EXECUTION_SCOPE_WITH_CHECK(execCtx);
309 auto params = ConvertToJSValues(offset);
310 func->Call(JSRef<JSObject>(), params.size(), params.data());
311 return;
312 };
313 }
314
315 // use SetDeleteArea to update builder function
316 ListItemModel::GetInstance()->SetSwiperAction(nullptr, nullptr, std::move(onOffsetChangeCallback), swipeEdgeEffect);
317 }
318
SelectCallback(const JSCallbackInfo & args)319 void JSListItem::SelectCallback(const JSCallbackInfo& args)
320 {
321 if (!args[0]->IsFunction()) {
322 return;
323 }
324
325 RefPtr<JsMouseFunction> jsOnSelectFunc = AceType::MakeRefPtr<JsMouseFunction>(JSRef<JSFunc>::Cast(args[0]));
326 auto onSelect = [execCtx = args.GetExecutionContext(), func = std::move(jsOnSelectFunc)](bool isSelected) {
327 JAVASCRIPT_EXECUTION_SCOPE_WITH_CHECK(execCtx);
328 func->SelectExecute(isSelected);
329 };
330 ListItemModel::GetInstance()->SetSelectCallback(std::move(onSelect));
331 }
332
JsBorderRadius(const JSCallbackInfo & info)333 void JSListItem::JsBorderRadius(const JSCallbackInfo& info)
334 {
335 JSViewAbstract::JsBorderRadius(info);
336 CalcDimension borderRadius;
337 if (!JSViewAbstract::ParseJsDimensionVp(info[0], borderRadius)) {
338 return;
339 }
340 ListItemModel::GetInstance()->SetBorderRadius(borderRadius);
341 }
342
JsOnDragStart(const JSCallbackInfo & info)343 void JSListItem::JsOnDragStart(const JSCallbackInfo& info)
344 {
345 if (!info[0]->IsFunction()) {
346 return;
347 }
348 RefPtr<JsDragFunction> jsOnDragStartFunc = AceType::MakeRefPtr<JsDragFunction>(JSRef<JSFunc>::Cast(info[0]));
349 WeakPtr<NG::FrameNode> frameNode = AceType::WeakClaim(NG::ViewStackProcessor::GetInstance()->GetMainFrameNode());
350 auto onDragStart = [execCtx = info.GetExecutionContext(), func = std::move(jsOnDragStartFunc),
351 targetNode = frameNode](
352 const RefPtr<DragEvent>& info, const std::string& extraParams) -> NG::DragDropBaseInfo {
353 NG::DragDropBaseInfo itemInfo;
354 JAVASCRIPT_EXECUTION_SCOPE_WITH_CHECK(execCtx, itemInfo);
355 PipelineContext::SetCallBackNode(targetNode);
356 auto ret = func->Execute(info, extraParams);
357 if (!ret->IsObject()) {
358 return itemInfo;
359 }
360 auto node = ParseDragNode(ret);
361 if (node) {
362 itemInfo.node = node;
363 return itemInfo;
364 }
365
366 auto builderObj = JSRef<JSObject>::Cast(ret);
367 #if defined(PIXEL_MAP_SUPPORTED)
368 auto pixmap = builderObj->GetProperty("pixelMap");
369 itemInfo.pixelMap = CreatePixelMapFromNapiValue(pixmap);
370 #endif
371 auto extraInfo = builderObj->GetProperty("extraInfo");
372 ParseJsString(extraInfo, itemInfo.extraInfo);
373 node = ParseDragNode(builderObj->GetProperty("builder"));
374 itemInfo.node = node;
375 return itemInfo;
376 };
377 #ifdef NG_BUILD
378 ViewAbstractModel::GetInstance()->SetOnDragStart(std::move(onDragStart));
379 #else
380 if (Container::IsCurrentUseNewPipeline()) {
381 ViewAbstractModel::GetInstance()->SetOnDragStart(std::move(onDragStart));
382 } else {
383 ListItemModel::GetInstance()->SetOnDragStart(std::move(onDragStart));
384 }
385 #endif
386 }
387
JSBind(BindingTarget globalObj)388 void JSListItem::JSBind(BindingTarget globalObj)
389 {
390 JSClass<JSListItem>::Declare("ListItem");
391 JSClass<JSListItem>::StaticMethod("createInternal", &JSListItem::Create);
392 JSClass<JSListItem>::StaticMethod("create", &JSListItem::Create);
393 JSClass<JSListItem>::StaticMethod("pop", &JSListItem::Pop);
394
395 JSClass<JSListItem>::StaticMethod("sticky", &JSListItem::SetSticky);
396 JSClass<JSListItem>::StaticMethod("editable", &JSListItem::SetEditable);
397 JSClass<JSListItem>::StaticMethod("selectable", &JSListItem::SetSelectable);
398 JSClass<JSListItem>::StaticMethod("onSelect", &JSListItem::SelectCallback);
399 JSClass<JSListItem>::StaticMethod("borderRadius", &JSListItem::JsBorderRadius);
400 JSClass<JSListItem>::StaticMethod("swipeAction", &JSListItem::SetSwiperAction);
401 JSClass<JSListItem>::StaticMethod("selected", &JSListItem::SetSelected);
402
403 JSClass<JSListItem>::StaticMethod("onClick", &JSInteractableView::JsOnClick);
404 JSClass<JSListItem>::StaticMethod("onAttach", &JSInteractableView::JsOnAttach);
405 JSClass<JSListItem>::StaticMethod("onAppear", &JSInteractableView::JsOnAppear);
406 JSClass<JSListItem>::StaticMethod("onDetach", &JSInteractableView::JsOnDetach);
407 JSClass<JSListItem>::StaticMethod("onDisAppear", &JSInteractableView::JsOnDisAppear);
408 JSClass<JSListItem>::StaticMethod("onTouch", &JSInteractableView::JsOnTouch);
409 JSClass<JSListItem>::StaticMethod("onHover", &JSInteractableView::JsOnHover);
410 JSClass<JSListItem>::StaticMethod("onKeyEvent", &JSInteractableView::JsOnKey);
411 JSClass<JSListItem>::StaticMethod("onDeleteEvent", &JSInteractableView::JsOnDelete);
412 JSClass<JSListItem>::StaticMethod("remoteMessage", &JSInteractableView::JsCommonRemoteMessage);
413 JSClass<JSListItem>::StaticMethod("onDragStart", &JSListItem::JsOnDragStart);
414
415 JSClass<JSListItem>::InheritAndBind<JSContainerBase>(globalObj);
416 }
417
418 } // namespace OHOS::Ace::Framework
419