• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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