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 "frameworks/bridge/declarative_frontend/jsview/js_lazy_foreach.h"
17
18 #include <functional>
19 #include <set>
20 #include <string>
21
22 #include "base/memory/ace_type.h"
23 #include "base/memory/referenced.h"
24 #include "base/utils/utils.h"
25 #include "bridge/common/utils/utils.h"
26 #include "bridge/declarative_frontend/ark_theme/theme_apply/js_lazy_foreach_theme.h"
27 #include "bridge/declarative_frontend/engine/js_object_template.h"
28 #include "bridge/declarative_frontend/jsview/js_lazy_foreach_actuator.h"
29 #include "bridge/declarative_frontend/jsview/js_lazy_foreach_builder.h"
30 #ifndef NG_BUILD
31 #include "bridge/declarative_frontend/jsview/js_lazy_foreach_component.h"
32 #endif
33 #include "bridge/declarative_frontend/jsview/js_view.h"
34 #include "bridge/declarative_frontend/jsview/js_view_common_def.h"
35 #include "bridge/declarative_frontend/jsview/models/lazy_for_each_model_impl.h"
36 #include "bridge/declarative_frontend/view_stack_processor.h"
37 #include "core/common/container.h"
38 #include "core/common/container_scope.h"
39 #include "core/components_ng/base/view_stack_model.h"
40 #include "core/components_ng/syntax/lazy_for_each_model.h"
41 #include "core/components_ng/syntax/lazy_for_each_model_ng.h"
42
43 namespace OHOS::Ace {
GetInstance()44 LazyForEachModel* LazyForEachModel::GetInstance()
45 {
46 #ifdef NG_BUILD
47 static NG::LazyForEachModelNG instance;
48 return &instance;
49 #else
50 if (Container::IsCurrentUseNewPipeline()) {
51 static NG::LazyForEachModelNG instance;
52 return &instance;
53 } else {
54 static Framework::LazyForEachModelImpl instance;
55 return &instance;
56 }
57 #endif
58 }
59 } // namespace OHOS::Ace
60
61 namespace OHOS::Ace::Framework {
62
JSBind(BindingTarget globalObj)63 void JSDataChangeListener::JSBind(BindingTarget globalObj)
64 {
65 JSClass<JSDataChangeListener>::Declare("__ohos_ace_inner_JSDataChangeListener__");
66 // API7 onEditChanged deprecated
67 JSClass<JSDataChangeListener>::CustomMethod("onDataReloaded", &JSDataChangeListener::OnDataReloaded);
68 JSClass<JSDataChangeListener>::CustomMethod("onDataReload", &JSDataChangeListener::OnDataReloaded);
69 // API7 onDataAdded deprecated
70 JSClass<JSDataChangeListener>::CustomMethod("onDataAdded", &JSDataChangeListener::OnDataAdded);
71 JSClass<JSDataChangeListener>::CustomMethod("onDataAdd", &JSDataChangeListener::OnDataAdded);
72 // API7 onDataDeleted deprecated
73 JSClass<JSDataChangeListener>::CustomMethod("onDataDeleted", &JSDataChangeListener::OnDataDeleted);
74 JSClass<JSDataChangeListener>::CustomMethod("onDataDelete", &JSDataChangeListener::OnDataDeleted);
75 // API7 onDataChanged deprecated
76 JSClass<JSDataChangeListener>::CustomMethod("onDataChanged", &JSDataChangeListener::OnDataChanged);
77 JSClass<JSDataChangeListener>::CustomMethod("onDataChange", &JSDataChangeListener::OnDataChanged);
78 // API7 onDataMoved deprecated
79 JSClass<JSDataChangeListener>::CustomMethod("onDataMoved", &JSDataChangeListener::OnDataMoved);
80 JSClass<JSDataChangeListener>::CustomMethod("onDataMove", &JSDataChangeListener::OnDataMoved);
81 // temporary interface
82 JSClass<JSDataChangeListener>::CustomMethod("onDataBulkAdded", &JSDataChangeListener::OnDataBulkAdded);
83 JSClass<JSDataChangeListener>::CustomMethod("onDataBulkAdd", &JSDataChangeListener::OnDataBulkAdded);
84 JSClass<JSDataChangeListener>::CustomMethod("onDataBulkDeleted", &JSDataChangeListener::OnDataBulkDeleted);
85 JSClass<JSDataChangeListener>::CustomMethod("onDataBulkDelete", &JSDataChangeListener::OnDataBulkDeleted);
86 // API12 onDatasetChange
87 JSClass<JSDataChangeListener>::CustomMethod("onDatasetChange", &JSDataChangeListener::OnDatasetChange);
88 JSClass<JSDataChangeListener>::Bind(
89 globalObj, &JSDataChangeListener::Constructor, &JSDataChangeListener::Destructor);
90 }
91
CreateActuator(const std::string & viewId)92 RefPtr<JSLazyForEachActuator> CreateActuator(const std::string& viewId)
93 {
94 #ifdef NG_BUILD
95 return AceType::MakeRefPtr<JSLazyForEachBuilder>();
96 #else
97 if (Container::IsCurrentUseNewPipeline()) {
98 return AceType::MakeRefPtr<JSLazyForEachBuilder>();
99 } else {
100 return AceType::MakeRefPtr<JSLazyForEachComponent>(viewId);
101 }
102 #endif
103 }
104
105 namespace {
106
107 enum {
108 PARAM_VIEW_ID = 0,
109 PARAM_PARENT_VIEW,
110 PARAM_DATA_SOURCE,
111 PARAM_ITEM_GENERATOR,
112 PARAM_KEY_GENERATOR,
113 PARAM_UPDATE_CHANGEDNODE,
114
115 MIN_PARAM_SIZE = PARAM_KEY_GENERATOR,
116 MAX_PARAM_SIZE = 6,
117 };
118
ParseAndVerifyParams(const JSCallbackInfo & info,JSRef<JSVal> (& params)[MAX_PARAM_SIZE])119 bool ParseAndVerifyParams(const JSCallbackInfo& info, JSRef<JSVal> (¶ms)[MAX_PARAM_SIZE])
120 {
121 if (info.Length() < MIN_PARAM_SIZE) {
122 return false;
123 }
124
125 if (!info[PARAM_VIEW_ID]->IsNumber() && !info[PARAM_VIEW_ID]->IsString()) {
126 return false;
127 }
128 if (!info[PARAM_PARENT_VIEW]->IsObject()) {
129 return false;
130 }
131 if (!info[PARAM_DATA_SOURCE]->IsObject()) {
132 return false;
133 }
134 if (!info[PARAM_ITEM_GENERATOR]->IsFunction()) {
135 return false;
136 }
137 if (info.Length() > MIN_PARAM_SIZE &&
138 !(info[PARAM_KEY_GENERATOR]->IsFunction() || info[PARAM_KEY_GENERATOR]->IsUndefined())) {
139 return false;
140 }
141 if (info.Length() > MIN_PARAM_SIZE + 1 && !info[PARAM_UPDATE_CHANGEDNODE]->IsBoolean()) {
142 return false;
143 }
144
145 for (uint32_t idx = PARAM_VIEW_ID; idx < std::min(info.Length(), static_cast<uint32_t>(MAX_PARAM_SIZE)); ++idx) {
146 params[idx] = info[idx];
147 }
148 return true;
149 }
150
151 } // namespace
152
JSBind(BindingTarget globalObj)153 void JSLazyForEach::JSBind(BindingTarget globalObj)
154 {
155 JSClass<JSLazyForEach>::Declare("LazyForEach");
156 JSClass<JSLazyForEach>::StaticMethod("create", &JSLazyForEach::Create);
157 JSClass<JSLazyForEach>::StaticMethod("pop", &JSLazyForEach::Pop);
158 JSClass<JSLazyForEach>::StaticMethod("onMove", &JSLazyForEach::OnMove);
159 JSClass<JSLazyForEach>::Bind(globalObj);
160
161 JSDataChangeListener::JSBind(globalObj);
162 }
163
Create(const JSCallbackInfo & info)164 void JSLazyForEach::Create(const JSCallbackInfo& info)
165 {
166 JSRef<JSVal> params[MAX_PARAM_SIZE];
167 if (!ParseAndVerifyParams(info, params)) {
168 TAG_LOGW(AceLogTag::ACE_LAZY_FOREACH, "Invalid arguments for LazyForEach");
169 return;
170 }
171 if (!params[PARAM_PARENT_VIEW]->IsObject()|| !params[PARAM_DATA_SOURCE]->IsObject()
172 || !params[PARAM_ITEM_GENERATOR]->IsFunction() || !params[PARAM_VIEW_ID]->IsString()) {
173 return;
174 }
175 std::string viewId = ViewStackModel::GetInstance()->ProcessViewId(params[PARAM_VIEW_ID]->ToString());
176
177 JSRef<JSObject> parentViewObj = JSRef<JSObject>::Cast(params[PARAM_PARENT_VIEW]);
178
179 // LazyForEach is not in observeComponentCreation, mark isDeleting_ here
180 JSRef<JSVal> isDeleting = parentViewObj->GetProperty("isDeleting_");
181 if (isDeleting->IsBoolean() && isDeleting->ToBoolean()) {
182 return;
183 }
184
185 JSRef<JSObject> dataSourceObj = JSRef<JSObject>::Cast(params[PARAM_DATA_SOURCE]);
186 JSRef<JSFunc> itemGenerator = JSRef<JSFunc>::Cast(params[PARAM_ITEM_GENERATOR]);
187 JSLazyForEachTheme::ObtainItemGeneratorForThemeSupport(info.GetVm(), itemGenerator);
188 ItemKeyGenerator keyGenFunc;
189 bool updateChangedNodeFlag = false;
190
191 if (params[PARAM_KEY_GENERATOR]->IsUndefined()) {
192 keyGenFunc = [viewId](const JSRef<JSVal>&, size_t index) { return viewId + "-" + std::to_string(index); };
193 } else if (params[PARAM_KEY_GENERATOR]->IsFunction()) {
194 keyGenFunc = [viewId, keyGenerator = JSRef<JSFunc>::Cast(params[PARAM_KEY_GENERATOR])](
195 const JSRef<JSVal>& jsVal, size_t index) {
196 JSRef<JSVal> params[] = { jsVal, JSRef<JSVal>::Make(ToJSValue(index)) };
197 auto key = keyGenerator->Call(JSRef<JSObject>(), ArraySize(params), params);
198 return viewId + "-" + (key->IsString() || key->IsNumber() ? key->ToString() : std::to_string(index));
199 };
200 }
201
202 if (!params[PARAM_UPDATE_CHANGEDNODE]->IsUndefined()) {
203 updateChangedNodeFlag = params[PARAM_UPDATE_CHANGEDNODE]->ToBoolean();
204 }
205
206 const auto& actuator = CreateActuator(viewId);
207 actuator->SetJSExecutionContext(info.GetExecutionContext());
208 actuator->SetParentViewObj(parentViewObj);
209 actuator->SetDataSourceObj(dataSourceObj);
210 actuator->SetItemGenerator(itemGenerator, std::move(keyGenFunc));
211 actuator->SetUpdateChangedNodeFlag(updateChangedNodeFlag);
212 if (ViewStackModel::GetInstance()->IsPrebuilding()) {
213 auto createFunc = [actuator]() {
214 LazyForEachModel::GetInstance()->Create(actuator);
215 };
216 return ViewStackModel::GetInstance()->PushPrebuildCompCmd("[JSLazyForEach][create]", createFunc);
217 }
218 LazyForEachModel::GetInstance()->Create(actuator);
219 }
220
Pop()221 void JSLazyForEach::Pop()
222 {
223 if (ViewStackModel::GetInstance()->IsPrebuilding()) {
224 return ViewStackModel::GetInstance()->PushPrebuildCompCmd("[JSLazyForEach][pop]", &JSLazyForEach::Pop);
225 }
226 auto* stack = NG::ViewStackProcessor::GetInstance();
227 if (stack->GetMainFrameNode() && stack->GetMainFrameNode()->GetTag() == V2::TABS_ETS_TAG) {
228 return;
229 }
230 ViewStackModel::GetInstance()->PopContainer();
231 }
232
OnMove(const JSCallbackInfo & info)233 void JSLazyForEach::OnMove(const JSCallbackInfo& info)
234 {
235 if (info[0]->IsFunction()) {
236 auto context = info.GetExecutionContext();
237 auto onMove = [execCtx = context, func = JSRef<JSFunc>::Cast(info[0])](int32_t from, int32_t to) {
238 auto params = ConvertToJSValues(from, to);
239 func->Call(JSRef<JSObject>(), params.size(), params.data());
240 };
241 LazyForEachModel::GetInstance()->OnMove(std::move(onMove));
242 if ((info.Length() > 1) && info[1]->IsObject()) {
243 JsParseItemDragEventHandler(context, info[1]);
244 } else {
245 LazyForEachModel::GetInstance()->SetItemDragHandler(nullptr, nullptr, nullptr, nullptr);
246 }
247 } else {
248 LazyForEachModel::GetInstance()->OnMove(nullptr);
249 LazyForEachModel::GetInstance()->SetItemDragHandler(nullptr, nullptr, nullptr, nullptr);
250 }
251 }
252
JsParseItemDragEventHandler(const JsiExecutionContext & context,const JSRef<JSObject> & itemDragEventObj)253 void JSLazyForEach::JsParseItemDragEventHandler(
254 const JsiExecutionContext& context, const JSRef<JSObject>& itemDragEventObj)
255 {
256 auto onLongPress = itemDragEventObj->GetProperty("onLongPress");
257 std::function<void(int32_t)> onLongPressCallback;
258 if (onLongPress->IsFunction()) {
259 onLongPressCallback = [execCtx = context, func = JSRef<JSFunc>::Cast(onLongPress)](int32_t index) {
260 auto params = ConvertToJSValues(index);
261 func->Call(JSRef<JSObject>(), params.size(), params.data());
262 };
263 }
264
265 auto onDragStart = itemDragEventObj->GetProperty("onDragStart");
266 std::function<void(int32_t)> onDragStartCallback;
267 if (onDragStart->IsFunction()) {
268 onDragStartCallback = [execCtx = context, func = JSRef<JSFunc>::Cast(onDragStart)](int32_t index) {
269 auto params = ConvertToJSValues(index);
270 func->Call(JSRef<JSObject>(), params.size(), params.data());
271 };
272 }
273
274 auto onMoveThrough = itemDragEventObj->GetProperty("onMoveThrough");
275 std::function<void(int32_t, int32_t)> onMoveThroughCallback;
276 if (onMoveThrough->IsFunction()) {
277 onMoveThroughCallback = [execCtx = context, func = JSRef<JSFunc>::Cast(onMoveThrough)](
278 int32_t from, int32_t to) {
279 auto params = ConvertToJSValues(from, to);
280 func->Call(JSRef<JSObject>(), params.size(), params.data());
281 };
282 }
283
284 auto onDrop = itemDragEventObj->GetProperty("onDrop");
285 std::function<void(int32_t)> onDropCallback;
286 if (onDrop->IsFunction()) {
287 onDropCallback = [execCtx = context, func = JSRef<JSFunc>::Cast(onDrop)](int32_t index) {
288 auto params = ConvertToJSValues(index);
289 func->Call(JSRef<JSObject>(), params.size(), params.data());
290 };
291 }
292 LazyForEachModel::GetInstance()->SetItemDragHandler(std::move(onLongPressCallback), std::move(onDragStartCallback),
293 std::move(onMoveThroughCallback), std::move(onDropCallback));
294 }
295 } // namespace OHOS::Ace::Framework
296