1 /*
2 * Copyright (c) 2021 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_foreach.h"
17
18 #include <string>
19
20 #include "base/memory/referenced.h"
21 #include "core/components_ng/syntax/for_each_model_ng.h"
22 #include "bridge/declarative_frontend/jsview/js_view_common_def.h"
23 #include "bridge/declarative_frontend/jsview/models/for_each_model_impl.h"
24 #include "bridge/declarative_frontend/engine/functions/js_foreach_function.h"
25 #include "bridge/declarative_frontend/view_stack_processor.h"
26 #include "core/common/container.h"
27 #include "core/components_ng/base/view_stack_model.h"
28
29
30 namespace OHOS::Ace {
GetInstance()31 ForEachModel* ForEachModel::GetInstance()
32 {
33 #ifdef NG_BUILD
34 static NG::ForEachModelNG instance;
35 return &instance;
36 #else
37 if (Container::IsCurrentUseNewPipeline()) {
38 static NG::ForEachModelNG instance;
39 return &instance;
40 } else {
41 static Framework::ForEachModelImpl instance;
42 return &instance;
43 }
44 #endif
45 }
46 } // namespace OHOS::Ace
47
48 namespace {
49
50 enum {
51 PARAM_ELMT_ID = 0,
52 PARAM_JS_ARRAY = 1,
53 PARAM_DIFF_ID = 2,
54 PARAM_DUPLICATE_ID = 3,
55 PARAM_DELETE_ID = 4,
56 PARAM_ID_ARRAY_LENGTH = 5,
57 };
58 } // namespace
59
60 namespace OHOS::Ace::Framework {
61 // Create(...)
62 // NG: no params
63 // Classic: cmpilerGenId, array, itemGenFunc, idGenFunction
Create(const JSCallbackInfo & info)64 void JSForEach::Create(const JSCallbackInfo& info)
65 {
66 if (Container::IsCurrentUseNewPipeline()) {
67 ForEachModel::GetInstance()->Create();
68 return;
69 }
70
71 if (info.Length() < 4 || !info[2]->IsObject() || !info[3]->IsFunction() ||
72 (!info[0]->IsNumber() && !info[0]->IsString()) || info[1]->IsUndefined() || !info[1]->IsObject()) {
73 TAG_LOGW(AceLogTag::ACE_FOREACH, "Invalid arguments for ForEach");
74 return;
75 }
76
77 JSRef<JSObject> jsArray = JSRef<JSObject>::Cast(info[2]);
78 JSRef<JSVal> jsViewMapperFunc = info[3];
79 JSRef<JSVal> jsIdentityMapperFunc;
80 RefPtr<JsForEachFunction> jsForEachFunction;
81 if (info.Length() > 4 && info[4]->IsFunction()) {
82 jsIdentityMapperFunc = info[4];
83 jsForEachFunction = AceType::MakeRefPtr<JsForEachFunction>(
84 jsArray, JSRef<JSFunc>::Cast(jsIdentityMapperFunc), JSRef<JSFunc>::Cast(jsViewMapperFunc));
85 } else {
86 jsForEachFunction = AceType::MakeRefPtr<JsForEachFunction>(jsArray, JSRef<JSFunc>::Cast(jsViewMapperFunc));
87 }
88
89 OHOS::Ace::ForEachFunc forEachFunc = {
90 [jsForEachFunction]() { return jsForEachFunction->ExecuteIdentityMapper(); },
91 [jsForEachFunction](int32_t index) { jsForEachFunction->ExecuteBuilderForIndex(index); } };
92 ForEachModel::GetInstance()->Create(info[0]->ToString(), forEachFunc);
93 }
94
Pop()95 void JSForEach::Pop()
96 {
97 if (ViewStackModel::GetInstance()->IsPrebuilding()) {
98 return ViewStackModel::GetInstance()->PushPrebuildCompCmd("[JSForEach][pop]", &JSForEach::Pop);
99 }
100 ForEachModel::GetInstance()->Pop();
101 }
102
103 // partial update / NG only
104 // signature
105 // nodeId/elmtId : number
106 // idList : string[]
107 // returns bool, true on success
GetIdArray(const JSCallbackInfo & info)108 void JSForEach::GetIdArray(const JSCallbackInfo& info)
109 {
110 if ((info.Length() != 2) || !info[1]->IsArray() || info[0]->IsString()) {
111 TAG_LOGW(AceLogTag::ACE_FOREACH, "Invalid arguments for ForEach.GetIdArray");
112 info.SetReturnValue(JSRef<JSVal>::Make(ToJSValue(false)));
113 return;
114 }
115
116 JSRef<JSArray> jsArr = JSRef<JSArray>::Cast(info[1]);
117 if (jsArr->Length() > 0) {
118 TAG_LOGW(AceLogTag::ACE_FOREACH, "JS Array must be empty!");
119 info.SetReturnValue(JSRef<JSVal>::Make(ToJSValue(false)));
120 return;
121 }
122 if (!info[0]->IsNumber()) {
123 return;
124 }
125 const auto elmtId = info[0]->ToNumber<int32_t>();
126 std::list<std::string> idList = ForEachModel::GetInstance()->GetCurrentIdList(elmtId);
127
128 size_t index = 0;
129 for (const auto& id : idList) {
130 jsArr->SetValueAt(index++, JSRef<JSVal>::Make(ToJSValue(id.c_str())));
131 }
132 info.SetReturnValue(JSRef<JSVal>::Make(ToJSValue(index > 0)));
133 }
134
135 // Partial update / NG only
136 // Gets idList as a input and stores it.
137 // Fill diffIds with new indexes as an output.
138 // Fill duplicateIds with duplica IDs detected.
139 // nodeId/elmtId : number
140 // idList : string[]
141 // diffIds : number[]
142 // duplicateIds : number[]
143 // no return value
SetIdArray(const JSCallbackInfo & info)144 void JSForEach::SetIdArray(const JSCallbackInfo& info)
145 {
146 if (info.Length() != PARAM_ID_ARRAY_LENGTH || !info[PARAM_ELMT_ID]->IsNumber() ||
147 !info[PARAM_JS_ARRAY]->IsArray() || !info[PARAM_DIFF_ID]->IsArray() ||
148 !info[PARAM_DUPLICATE_ID]->IsArray() || !info[PARAM_DELETE_ID]->IsArray()) {
149 TAG_LOGW(AceLogTag::ACE_FOREACH, "Invalid arguments for ForEach.SetIdArray");
150 return;
151 }
152
153 const auto elmtId = info[PARAM_ELMT_ID]->ToNumber<int32_t>();
154 JSRef<JSArray> jsArr = JSRef<JSArray>::Cast(info[PARAM_JS_ARRAY]);
155 JSRef<JSArray> diffIds = JSRef<JSArray>::Cast(info[PARAM_DIFF_ID]);
156 JSRef<JSArray> duplicateIds = JSRef<JSArray>::Cast(info[PARAM_DUPLICATE_ID]);
157 std::list<std::string> newIdArr;
158
159 if (diffIds->Length() > 0 || duplicateIds->Length() > 0) {
160 TAG_LOGW(AceLogTag::ACE_FOREACH, "Invalid arguments for ForEach.SetIdArray output arrays must be empty!");
161 return;
162 }
163
164 const std::list<std::string>& previousIDList = ForEachModel::GetInstance()->GetCurrentIdList(elmtId);
165 std::unordered_set<std::string> oldIdsSet(previousIDList.begin(), previousIDList.end());
166 std::unordered_set<std::string> newIds;
167
168 size_t diffIndx = 0;
169 size_t duplicateIndx = 0;
170 for (size_t i = 0; i < jsArr->Length(); i++) {
171 JSRef<JSVal> strId = jsArr->GetValueAt(i);
172 // Save return value of insert to know was it duplicate...
173 std::pair<std::unordered_set<std::string>::iterator, bool> ret = newIds.insert(strId->ToString());
174 // Duplicate Id detected. Will return index of those to caller.
175 if (!ret.second) {
176 duplicateIds->SetValueAt(duplicateIndx++, JSRef<JSVal>::Make(ToJSValue(i)));
177 } else {
178 // ID was not duplicate. Accept it.
179 newIdArr.emplace_back(*ret.first);
180 // Check was ID previously available or totally new one.
181 if (oldIdsSet.find(*ret.first) == oldIdsSet.end()) {
182 // Populate output diff array with this index that was not in old array.
183 diffIds->SetValueAt(diffIndx++, JSRef<JSVal>::Make(ToJSValue(i)));
184 }
185 }
186 }
187 ForEachModel::GetInstance()->SetNewIds(std::move(newIdArr));
188
189 std::list<int32_t> removedElmtIds;
190 ForEachModel::GetInstance()->SetRemovedElmtIds(removedElmtIds);
191
192 if (removedElmtIds.size()) {
193 JSRef<JSArray> jsArr = JSRef<JSArray>::Cast(info[PARAM_DELETE_ID]);
194 size_t index = jsArr->Length();
195
196 for (const auto& rmElmtId : removedElmtIds) {
197 jsArr->SetValueAt(index++, JSRef<JSVal>::Make(ToJSValue(rmElmtId)));
198 }
199 }
200 }
201
202 // signature is
203 // id: string | number
204 // parentView : JSView
CreateNewChildStart(const JSCallbackInfo & info)205 void JSForEach::CreateNewChildStart(const JSCallbackInfo& info)
206 {
207 if ((info.Length() != 2) || !info[1]->IsObject() || (!info[0]->IsNumber() && !info[0]->IsString())) {
208 return;
209 }
210
211 const auto id = info[0]->ToString();
212 ForEachModel::GetInstance()->CreateNewChildStart(id);
213 }
214
215 // signature is
216 // id: string | number
217 // parentView : JSView
CreateNewChildFinish(const JSCallbackInfo & info)218 void JSForEach::CreateNewChildFinish(const JSCallbackInfo& info)
219 {
220 if ((info.Length() != 2) || !info[1]->IsObject() || (!info[0]->IsNumber() && !info[0]->IsString())) {
221 return;
222 }
223
224 const auto id = info[0]->ToString();
225 ForEachModel::GetInstance()->CreateNewChildFinish(id);
226 }
227
OnMove(const JSCallbackInfo & info)228 void JSForEach::OnMove(const JSCallbackInfo& info)
229 {
230 if (info[0]->IsFunction()) {
231 auto context = info.GetExecutionContext();
232 auto onMove = [execCtx = context, func = JSRef<JSFunc>::Cast(info[0])](int32_t from, int32_t to) {
233 auto params = ConvertToJSValues(from, to);
234 func->Call(JSRef<JSObject>(), params.size(), params.data());
235 };
236 ForEachModel::GetInstance()->OnMove(std::move(onMove));
237 if ((info.Length() > 1) && info[1]->IsObject()) {
238 JsParseItemDragEventHandler(context, info[1]);
239 } else {
240 ForEachModel::GetInstance()->SetItemDragHandler(nullptr, nullptr, nullptr, nullptr);
241 }
242 } else {
243 ForEachModel::GetInstance()->OnMove(nullptr);
244 ForEachModel::GetInstance()->SetItemDragHandler(nullptr, nullptr, nullptr, nullptr);
245 }
246 }
247
JsParseItemDragEventHandler(const JsiExecutionContext & context,const JSRef<JSObject> & itemDragEventObj)248 void JSForEach::JsParseItemDragEventHandler(const JsiExecutionContext& context, const JSRef<JSObject>& itemDragEventObj)
249 {
250 auto onLongPress = itemDragEventObj->GetProperty("onLongPress");
251 std::function<void(int32_t)> onLongPressCallback;
252 if (onLongPress->IsFunction()) {
253 onLongPressCallback = [execCtx = context, func = JSRef<JSFunc>::Cast(onLongPress)](int32_t index) {
254 auto params = ConvertToJSValues(index);
255 func->Call(JSRef<JSObject>(), params.size(), params.data());
256 };
257 }
258
259 auto onDragStart = itemDragEventObj->GetProperty("onDragStart");
260 std::function<void(int32_t)> onDragStartCallback;
261 if (onDragStart->IsFunction()) {
262 onDragStartCallback = [execCtx = context, func = JSRef<JSFunc>::Cast(onDragStart)](int32_t index) {
263 auto params = ConvertToJSValues(index);
264 func->Call(JSRef<JSObject>(), params.size(), params.data());
265 };
266 }
267
268 auto onMoveThrough = itemDragEventObj->GetProperty("onMoveThrough");
269 std::function<void(int32_t, int32_t)> onMoveThroughCallback;
270 if (onMoveThrough->IsFunction()) {
271 onMoveThroughCallback = [execCtx = context, func = JSRef<JSFunc>::Cast(onMoveThrough)](
272 int32_t from, int32_t to) {
273 auto params = ConvertToJSValues(from, to);
274 func->Call(JSRef<JSObject>(), params.size(), params.data());
275 };
276 }
277
278 auto onDrop = itemDragEventObj->GetProperty("onDrop");
279 std::function<void(int32_t)> onDropCallback;
280 if (onDrop->IsFunction()) {
281 onDropCallback = [execCtx = context, func = JSRef<JSFunc>::Cast(onDrop)](int32_t index) {
282 auto params = ConvertToJSValues(index);
283 func->Call(JSRef<JSObject>(), params.size(), params.data());
284 };
285 }
286 ForEachModel::GetInstance()->SetItemDragHandler(std::move(onLongPressCallback), std::move(onDragStartCallback),
287 std::move(onMoveThroughCallback), std::move(onDropCallback));
288 }
289
JSBind(BindingTarget globalObj)290 void JSForEach::JSBind(BindingTarget globalObj)
291 {
292 JSClass<JSForEach>::Declare("ForEach");
293 JSClass<JSForEach>::StaticMethod("create", &JSForEach::Create);
294 JSClass<JSForEach>::StaticMethod("pop", &JSForEach::Pop);
295 JSClass<JSForEach>::StaticMethod("getIdArray", &JSForEach::GetIdArray);
296 JSClass<JSForEach>::StaticMethod("setIdArray", &JSForEach::SetIdArray);
297 JSClass<JSForEach>::StaticMethod("createNewChildStart", &JSForEach::CreateNewChildStart);
298 JSClass<JSForEach>::StaticMethod("createNewChildFinish", &JSForEach::CreateNewChildFinish);
299 JSClass<JSForEach>::StaticMethod("onMove", &JSForEach::OnMove);
300 JSClass<JSForEach>::Bind<>(globalObj);
301 }
302
303 } // namespace OHOS::Ace::Framework
304