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