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