1 /*
2 * Copyright (c) 2025 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_repeat_virtual_scroll_2.h"
17
18 #include <string>
19
20 #include "base/log/ace_trace.h"
21 #include "base/log/log_wrapper.h"
22 #include "bridge/declarative_frontend/jsview/js_view_common_def.h"
23 #include "core/components_ng/syntax/repeat_virtual_scroll_2_model_ng.h"
24
25 namespace OHOS::Ace {
26
27 std::unique_ptr<RepeatVirtualScroll2Model> RepeatVirtualScroll2Model::instance_ = nullptr;
28 #define UNUSED_CACHED_SIZE_PARAM 2
29
GetInstance()30 RepeatVirtualScroll2Model* RepeatVirtualScroll2Model::GetInstance()
31 {
32 if (!instance_) {
33 instance_.reset(new NG::RepeatVirtualScroll2ModelNG());
34 }
35 return instance_.get();
36 }
37 } // namespace OHOS::Ace
38
39 namespace OHOS::Ace::Framework {
40
41 enum {
42 PARAM_ARR_LEN = 0,
43 PARAM_TOTAL_COUNT = 1,
44 PARAM_HANDLERS = 2,
45 PARAM_SIZE = 3,
46 };
47
ParseAndVerifyParams(const JSCallbackInfo & info)48 static bool ParseAndVerifyParams(const JSCallbackInfo& info)
49 {
50 if (info.Length() != PARAM_SIZE || !info[PARAM_ARR_LEN]->IsNumber() || !info[PARAM_TOTAL_COUNT]->IsNumber() ||
51 !info[PARAM_HANDLERS]->IsObject()) {
52 return false;
53 }
54
55 auto handlers = JSRef<JSObject>::Cast(info[PARAM_HANDLERS]);
56 return true;
57 }
58
Create(const JSCallbackInfo & info)59 void JSRepeatVirtualScroll2::Create(const JSCallbackInfo& info)
60 {
61 if (!ParseAndVerifyParams(info)) {
62 TAG_LOGW(AceLogTag::ACE_REPEAT, "Invalid arguments for RepeatVirtualScroll2");
63 return;
64 }
65
66 // arg 0 arrLen : number
67 auto arrLen = info[PARAM_ARR_LEN]->ToNumber<uint32_t>();
68
69 // arg 1 totalCount : number
70 auto totalCount = info[PARAM_TOTAL_COUNT]->ToNumber<uint32_t>();
71
72 // arg 2 onGetRid4Index(number int32_t) : number(uint32_t)
73 auto handlers = JSRef<JSObject>::Cast(info[PARAM_HANDLERS]);
74 auto onGetRid4IndexFunc = handlers->GetProperty("onGetRid4Index");
75 if (!onGetRid4IndexFunc->IsFunction()) {
76 return;
77 }
78 auto onGetRid4Index = [execCtx = info.GetExecutionContext(), func = JSRef<JSFunc>::Cast(onGetRid4IndexFunc)](
79 int32_t forIndex) -> std::pair<uint32_t, uint32_t> {
80 JAVASCRIPT_EXECUTION_SCOPE_WITH_CHECK(execCtx, std::pair<uint32_t, uint32_t>(0, 0));
81 auto params = ConvertToJSValues(forIndex);
82 JSRef<JSVal> jsVal = func->Call(JSRef<JSObject>(), params.size(), params.data());
83 // convert js-array to std::pair
84 if (!jsVal->IsArray() || JSRef<JSArray>::Cast(jsVal)->Length() != 2) {
85 TAG_LOGW(AceLogTag::ACE_REPEAT, "jsVal should be array.");
86 return std::pair<uint32_t, uint32_t>(0, 0);
87 }
88 JSRef<JSArray> jsArr = JSRef<JSArray>::Cast(jsVal);
89 return std::pair<uint32_t, uint32_t>(
90 jsArr->GetValueAt(0)->ToNumber<uint32_t>(), jsArr->GetValueAt(1)->ToNumber<uint32_t>());
91 };
92
93 auto onRecycleItemsFunc = handlers->GetProperty("onRecycleItems");
94 if (!onRecycleItemsFunc->IsFunction()) {
95 return;
96 }
97 auto onRecycleItems = [execCtx = info.GetExecutionContext(), func = JSRef<JSFunc>::Cast(onRecycleItemsFunc)](
98 int32_t fromIndex, int32_t toIndex) -> void {
99 JAVASCRIPT_EXECUTION_SCOPE_WITH_CHECK(execCtx);
100 auto params = ConvertToJSValues(fromIndex, toIndex);
101 func->Call(JSRef<JSObject>(), params.size(), params.data());
102 };
103
104 auto onActiveRangeFunc = handlers->GetProperty("onActiveRange");
105 if (!onActiveRangeFunc->IsFunction()) {
106 return;
107 }
108 auto onActiveRange = [execCtx = info.GetExecutionContext(), func = JSRef<JSFunc>::Cast(onActiveRangeFunc)](
109 int32_t fromIndex, int32_t toIndex, int32_t vStart, int32_t vEnd, bool isLoop, bool forceUpdate) -> void {
110 JAVASCRIPT_EXECUTION_SCOPE_WITH_CHECK(execCtx);
111 auto params = ConvertToJSValues(
112 fromIndex != INT32_MAX ? fromIndex : std::numeric_limits<double>::quiet_NaN(),
113 toIndex != INT32_MAX ? toIndex : std::numeric_limits<double>::quiet_NaN(),
114 vStart != INT32_MAX ? vStart : std::numeric_limits<double>::quiet_NaN(),
115 vEnd != INT32_MAX ? vEnd : std::numeric_limits<double>::quiet_NaN(),
116 isLoop, forceUpdate);
117 func->Call(JSRef<JSObject>(), params.size(), params.data());
118 };
119
120 auto onMoveFromToFunc = handlers->GetProperty("onMoveFromTo");
121 if (!onMoveFromToFunc->IsFunction()) {
122 return;
123 }
124 auto onMoveFromTo = [execCtx = info.GetExecutionContext(), func = JSRef<JSFunc>::Cast(onMoveFromToFunc)](
125 int32_t moveFrom, int32_t moveTo) -> void {
126 JAVASCRIPT_EXECUTION_SCOPE_WITH_CHECK(execCtx);
127 auto params = ConvertToJSValues(moveFrom, moveTo);
128 func->Call(JSRef<JSObject>(), params.size(), params.data());
129 };
130
131 auto onPurgeFunc = handlers->GetProperty("onPurge");
132 if (!onPurgeFunc->IsFunction()) {
133 return;
134 }
135 auto onPurge = [execCtx = info.GetExecutionContext(), func = JSRef<JSFunc>::Cast(onPurgeFunc)]() {
136 JSRef<JSVal> jsVal = func->Call(JSRef<JSObject>(), 0, nullptr);
137 };
138
139 RepeatVirtualScroll2Model::GetInstance()->Create(
140 arrLen, totalCount, onGetRid4Index, onRecycleItems, onActiveRange, onMoveFromTo, onPurge);
141 }
142
RemoveNode(const JSCallbackInfo & info)143 void JSRepeatVirtualScroll2::RemoveNode(const JSCallbackInfo& info)
144 {
145 ACE_SCOPED_TRACE("RepeatVirtualScroll:RemoveNode");
146 if (!info[0]->IsNumber()) {
147 TAG_LOGE(AceLogTag::ACE_REPEAT, "JSRepeatVirtualScroll2::RemoveNode - invalid parameter ERROR.");
148 return;
149 }
150 TAG_LOGD(AceLogTag::ACE_REPEAT, "JSRepeatVirtualScroll2::RemoveNode");
151 auto rid = info[0]->ToNumber<uint32_t>();
152 RepeatVirtualScroll2Model::GetInstance()->RemoveNode(rid);
153 }
154
155 // setInvalid(repeatElmtId : number, fromIndex : number)
SetInvalid(const JSCallbackInfo & info)156 void JSRepeatVirtualScroll2::SetInvalid(const JSCallbackInfo& info)
157 {
158 ACE_SCOPED_TRACE("RepeatVirtualScroll:SetInvalid");
159 if (!info[0]->IsNumber() || !info[1]->IsNumber()) {
160 TAG_LOGE(AceLogTag::ACE_REPEAT, "JSRepeatVirtualScroll2::SetInvalid - invalid parameter ERROR");
161 return;
162 }
163 TAG_LOGD(AceLogTag::ACE_REPEAT, "JSRepeatVirtualScroll2::SetInvalid");
164 auto repeatElmtId = info[0]->ToNumber<int32_t>();
165 auto rid = info[1]->ToNumber<uint32_t>();
166 RepeatVirtualScroll2Model::GetInstance()->SetInvalid(repeatElmtId, rid);
167 }
168
169 // requestContainerReLayout(repeatElmtId: number, totalCount: number, index: number): void;
RequestContainerReLayout(const JSCallbackInfo & info)170 void JSRepeatVirtualScroll2::RequestContainerReLayout(const JSCallbackInfo& info)
171 {
172 ACE_SCOPED_TRACE("RepeatVirtualScroll:RequestContainerReLayout");
173 enum {
174 PARAM_ELMTID = 0,
175 PARAM_ARR_LEN = 1,
176 PARAM_TOTAL_COUNT = 2,
177 PARAM_CHILD_INDEX = 3, // optional
178 };
179
180 if (!info[PARAM_ELMTID]->IsNumber() || !info[PARAM_ARR_LEN]->IsNumber() || !info[PARAM_TOTAL_COUNT]->IsNumber()) {
181 TAG_LOGE(AceLogTag::ACE_REPEAT, "JSRepeatVirtualScroll2::RequestContainerReLayout - invalid parameters ERROR");
182 return;
183 }
184
185 TAG_LOGD(AceLogTag::ACE_REPEAT, "JSRepeatVirtualScroll2::RequestContainerReLayout");
186 auto repeatElmtId = info[PARAM_ELMTID]->ToNumber<int32_t>();
187 auto arrLen = info[PARAM_ARR_LEN]->ToNumber<uint32_t>();
188 auto totalCount = info[PARAM_TOTAL_COUNT]->ToNumber<uint32_t>();
189
190 if (!info[PARAM_CHILD_INDEX]->IsNumber()) {
191 RepeatVirtualScroll2Model::GetInstance()->RequestContainerReLayout(repeatElmtId, arrLen, totalCount);
192 } else {
193 auto invalidateContainerLayoutFromChildIndex = info[PARAM_CHILD_INDEX]->ToNumber<int32_t>();
194 RepeatVirtualScroll2Model::GetInstance()->RequestContainerReLayout(
195 repeatElmtId, arrLen, totalCount, invalidateContainerLayoutFromChildIndex);
196 }
197 }
198
NotifyContainerLayoutChange(const JSCallbackInfo & info)199 void JSRepeatVirtualScroll2::NotifyContainerLayoutChange(const JSCallbackInfo& info)
200 {
201 ACE_SCOPED_TRACE("RepeatVirtualScroll:NotifyContainerLayoutChange");
202 enum {
203 PARAM_ELMTID = 0,
204 PARAM_ARR_LEN = 1,
205 PARAM_TOTAL_COUNT = 2,
206 PARAM_INDEX = 3,
207 PARAM_COUNT = 4,
208 PARAM_TYPE = 5,
209 };
210
211 if (!info[PARAM_ELMTID]->IsNumber() || !info[PARAM_ARR_LEN]->IsNumber() || !info[PARAM_TOTAL_COUNT]->IsNumber() ||
212 !info[PARAM_INDEX]->IsNumber() || !info[PARAM_COUNT]->IsNumber() || !info[PARAM_TYPE]->IsNumber()) {
213 TAG_LOGW(
214 AceLogTag::ACE_REPEAT, "JSRepeatVirtualScroll2::NotifyContainerLayoutChange - invalid parameters ERROR");
215 return;
216 }
217
218 auto repeatElmtId = info[PARAM_ELMTID]->ToNumber<int32_t>();
219 auto arrLen = info[PARAM_ARR_LEN]->ToNumber<uint32_t>();
220 auto totalCount = info[PARAM_TOTAL_COUNT]->ToNumber<int32_t>();
221 auto index = info[PARAM_INDEX]->ToNumber<int32_t>();
222 auto count = info[PARAM_COUNT]->ToNumber<int32_t>();
223 auto notificationType = static_cast<NG::UINode::NotificationType>(info[PARAM_TYPE]->ToNumber<int32_t>());
224
225 RepeatVirtualScroll2Model::GetInstance()->NotifyContainerLayoutChange(
226 repeatElmtId, arrLen, totalCount, index, count, notificationType);
227 }
228
229 // updateL1Rid4Index(repeatElmtId: number,
230 // totalCount: number,
231 // invalidateContainerLayoutFromChildIndex: number,
232 // l1rid4index: Array<Array<number>>): void;
UpdateL1Rid4Index(const JSCallbackInfo & info)233 void JSRepeatVirtualScroll2::UpdateL1Rid4Index(const JSCallbackInfo& info)
234 {
235 ACE_SCOPED_TRACE("RepeatVirtualScroll:UpdateL1Rid4Index");
236 enum {
237 PARAM_ELMTID = 0,
238 PARAM_ARR_LEN = 1,
239 PARAM_TOTAL_COUNT = 2,
240 PARAM_CHILD_INDEX = 3,
241 PARAM_ARRAY_PAIRS = 4,
242 PARAM_RECYCLE_RID = 5,
243 };
244
245 if (!info[PARAM_ELMTID]->IsNumber() || !info[PARAM_ARR_LEN]->IsNumber() || !info[PARAM_TOTAL_COUNT]->IsNumber() ||
246 !info[PARAM_CHILD_INDEX]->IsNumber() || !info[PARAM_ARRAY_PAIRS]->IsArray() ||
247 !info[PARAM_RECYCLE_RID]->IsArray()) {
248 TAG_LOGE(AceLogTag::ACE_REPEAT, "JSRepeatVirtualScroll2::UpdateL1Rid4Index - invalid parameters ERROR");
249 return;
250 }
251
252 TAG_LOGD(AceLogTag::ACE_REPEAT, "JSRepeatVirtualScroll2::UpdateL1Rid4Index");
253 auto repeatElmtId = info[PARAM_ELMTID]->ToNumber<int32_t>();
254 auto arrLen = info[PARAM_ARR_LEN]->ToNumber<uint32_t>();
255 auto totalCount = info[PARAM_TOTAL_COUNT]->ToNumber<uint32_t>();
256 auto invalidateContainerLayoutFromChildIndex = info[PARAM_CHILD_INDEX]->ToNumber<uint32_t>();
257
258 auto arrayOfPairs = JSRef<JSArray>::Cast(info[PARAM_ARRAY_PAIRS]);
259 std::map<int32_t, uint32_t> l1Rid4Index;
260 for (size_t i = 0; i < arrayOfPairs->Length(); i++) {
261 JSRef<JSArray> pair = arrayOfPairs->GetValueAt(i);
262 auto index = pair->GetValueAt(0)->ToNumber<int32_t>();
263 auto rid = pair->GetValueAt(1)->ToNumber<uint32_t>();
264 TAG_LOGD(AceLogTag::ACE_REPEAT, " ... index: %{public}d rid: %{public}d", index, static_cast<uint32_t>(rid));
265 l1Rid4Index[index] = rid;
266 }
267
268 auto arrayOfRidNeedToRecycle = JSRef<JSArray>::Cast(info[PARAM_RECYCLE_RID]);
269 std::unordered_set<uint32_t> ridNeedToRecycle;
270 for (size_t i = 0; i < arrayOfRidNeedToRecycle->Length(); i++) {
271 auto rid = arrayOfRidNeedToRecycle->GetValueAt(i);
272 if (rid->IsNumber()) {
273 ridNeedToRecycle.emplace(rid->ToNumber<uint32_t>());
274 }
275 }
276
277 RepeatVirtualScroll2Model::GetInstance()->UpdateL1Rid4Index(
278 repeatElmtId, arrLen, totalCount, invalidateContainerLayoutFromChildIndex, l1Rid4Index, ridNeedToRecycle);
279 }
280
OnMove(const JSCallbackInfo & info)281 void JSRepeatVirtualScroll2::OnMove(const JSCallbackInfo& info)
282 {
283 enum OnMoveParam {
284 ELMTID = 0,
285 ON_MOVE = 1,
286 ITEM_DRAG_HANDLER = 2,
287 };
288 if (!info[OnMoveParam::ELMTID]->IsNumber()) {
289 TAG_LOGE(AceLogTag::ACE_REPEAT, "JSRepeatVirtualScroll2::OnMove - invalid parameters ERROR");
290 return;
291 }
292 auto repeatElmtId = info[OnMoveParam::ELMTID]->ToNumber<int32_t>();
293 if (!info[OnMoveParam::ON_MOVE]->IsFunction()) {
294 RepeatVirtualScroll2Model::GetInstance()->OnMove(repeatElmtId, nullptr);
295 RepeatVirtualScroll2Model::GetInstance()->SetItemDragHandler(repeatElmtId, nullptr, nullptr, nullptr, nullptr);
296 return;
297 }
298 auto context = info.GetExecutionContext();
299 auto onMove = [execCtx = context, func = JSRef<JSFunc>::Cast(info[OnMoveParam::ON_MOVE])](
300 int32_t from, int32_t to) {
301 auto params = ConvertToJSValues(from, to);
302 func->Call(JSRef<JSObject>(), params.size(), params.data());
303 };
304 RepeatVirtualScroll2Model::GetInstance()->OnMove(repeatElmtId, std::move(onMove));
305 if ((info.Length() > 2) && info[ITEM_DRAG_HANDLER]->IsObject()) { // 2: Array length
306 JsParseItemDragEventHandler(context, info[ITEM_DRAG_HANDLER], repeatElmtId);
307 } else {
308 RepeatVirtualScroll2Model::GetInstance()->SetItemDragHandler(repeatElmtId, nullptr, nullptr, nullptr, nullptr);
309 }
310 }
311
JsParseItemDragEventHandler(const JsiExecutionContext & context,const JSRef<JSObject> & itemDragEventObj,int32_t repeatElmtId)312 void JSRepeatVirtualScroll2::JsParseItemDragEventHandler(
313 const JsiExecutionContext& context, const JSRef<JSObject>& itemDragEventObj, int32_t repeatElmtId)
314 {
315 auto onLongPress = itemDragEventObj->GetProperty("onLongPress");
316 std::function<void(int32_t)> onLongPressCallback;
317 if (onLongPress->IsFunction()) {
318 onLongPressCallback = [execCtx = context, func = JSRef<JSFunc>::Cast(onLongPress)](int32_t index) {
319 auto params = ConvertToJSValues(index);
320 func->Call(JSRef<JSObject>(), params.size(), params.data());
321 };
322 }
323
324 auto onDragStart = itemDragEventObj->GetProperty("onDragStart");
325 std::function<void(int32_t)> onDragStartCallback;
326 if (onDragStart->IsFunction()) {
327 onDragStartCallback = [execCtx = context, func = JSRef<JSFunc>::Cast(onDragStart)](int32_t index) {
328 auto params = ConvertToJSValues(index);
329 func->Call(JSRef<JSObject>(), params.size(), params.data());
330 };
331 }
332 auto onMoveThrough = itemDragEventObj->GetProperty("onMoveThrough");
333 std::function<void(int32_t, int32_t)> onMoveThroughCallback;
334 if (onMoveThrough->IsFunction()) {
335 onMoveThroughCallback = [execCtx = context, func = JSRef<JSFunc>::Cast(onMoveThrough)](
336 int32_t from, int32_t to) {
337 auto params = ConvertToJSValues(from, to);
338 func->Call(JSRef<JSObject>(), params.size(), params.data());
339 };
340 }
341 auto onDrop = itemDragEventObj->GetProperty("onDrop");
342 std::function<void(int32_t)> onDropCallback;
343 if (onDrop->IsFunction()) {
344 onDropCallback = [execCtx = context, func = JSRef<JSFunc>::Cast(onDrop)](int32_t index) {
345 auto params = ConvertToJSValues(index);
346 func->Call(JSRef<JSObject>(), params.size(), params.data());
347 };
348 }
349 RepeatVirtualScroll2Model::GetInstance()->SetItemDragHandler(repeatElmtId, std::move(onLongPressCallback),
350 std::move(onDragStartCallback), std::move(onMoveThroughCallback), std::move(onDropCallback));
351 }
352
SetCreateByTemplate(const JSCallbackInfo & info)353 void JSRepeatVirtualScroll2::SetCreateByTemplate(const JSCallbackInfo& info)
354 {
355 if (!info[0]->IsBoolean()) {
356 TAG_LOGE(AceLogTag::ACE_REPEAT, "JSRepeatVirtualScroll2::SetCreateByTemplate wrong parameter, internal error.");
357 return;
358 }
359 RepeatVirtualScroll2Model::GetInstance()->SetCreateByTemplate(info[0]->ToBoolean());
360 }
361
JSBind(BindingTarget globalObj)362 void JSRepeatVirtualScroll2::JSBind(BindingTarget globalObj)
363 {
364 JSClass<JSRepeatVirtualScroll2>::Declare("RepeatVirtualScroll2Native");
365 JSClass<JSRepeatVirtualScroll2>::StaticMethod("create", &JSRepeatVirtualScroll2::Create);
366
367 JSClass<JSRepeatVirtualScroll2>::StaticMethod("removeNode", &JSRepeatVirtualScroll2::RemoveNode);
368 JSClass<JSRepeatVirtualScroll2>::StaticMethod("setInvalid", &JSRepeatVirtualScroll2::SetInvalid);
369 JSClass<JSRepeatVirtualScroll2>::StaticMethod(
370 "requestContainerReLayout", &JSRepeatVirtualScroll2::RequestContainerReLayout);
371 JSClass<JSRepeatVirtualScroll2>::StaticMethod(
372 "notifyContainerLayoutChange", &JSRepeatVirtualScroll2::NotifyContainerLayoutChange);
373
374 JSClass<JSRepeatVirtualScroll2>::StaticMethod("updateL1Rid4Index", &JSRepeatVirtualScroll2::UpdateL1Rid4Index);
375
376 JSClass<JSRepeatVirtualScroll2>::StaticMethod("onMove", &JSRepeatVirtualScroll2::OnMove);
377 JSClass<JSRepeatVirtualScroll2>::StaticMethod("setCreateByTemplate", &JSRepeatVirtualScroll2::SetCreateByTemplate);
378 JSClass<JSRepeatVirtualScroll2>::Bind<>(globalObj);
379 }
380
381 } // namespace OHOS::Ace::Framework
382