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