/* * Copyright (c) 2021-2023 Huawei Device Co., Ltd. * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include "bridge/declarative_frontend/jsview/js_select.h" #include #include #include #include "base/log/ace_scoring_log.h" #include "base/utils/utils.h" #include "bridge/common/utils/utils.h" #include "bridge/declarative_frontend/engine/functions/js_function.h" #include "bridge/declarative_frontend/jsview/js_interactable_view.h" #include "bridge/declarative_frontend/jsview/js_view_common_def.h" #include "bridge/declarative_frontend/jsview/models/select_model_impl.h" #include "core/components_ng/base/view_abstract_model.h" #include "core/components_ng/pattern/select/select_model.h" #include "core/components_ng/pattern/select/select_model_ng.h" #include "core/components_v2/inspector/inspector_constants.h" #include "core/pipeline/pipeline_base.h" namespace OHOS::Ace { std::unique_ptr SelectModel::instance_ = nullptr; std::mutex SelectModel::mutex_; SelectModel* SelectModel::GetInstance() { if (!instance_) { std::lock_guard lock(mutex_); if (!instance_) { #ifdef NG_BUILD instance_.reset(new NG::SelectModelNG()); #else if (Container::IsCurrentUseNewPipeline()) { instance_.reset(new NG::SelectModelNG()); } else { instance_.reset(new Framework::SelectModelImpl()); } #endif } } return instance_.get(); } } // namespace OHOS::Ace namespace OHOS::Ace::Framework { void JSSelect::Create(const JSCallbackInfo& info) { if (info.Length() < 0) { return; } if (info[0]->IsArray()) { auto paramArray = JSRef::Cast(info[0]); size_t size = paramArray->Length(); std::vector params(size); for (size_t i = 0; i < size; i++) { std::string value; std::string icon; JSRef indexVal = paramArray->GetValueAt(i); if (!indexVal->IsObject()) { LOGE("element of paramArray is not an object."); return; } auto indexObject = JSRef::Cast(indexVal); auto selectValue = indexObject->GetProperty("value"); auto selectIcon = indexObject->GetProperty("icon"); if (!ParseJsString(selectValue, value)) { LOGW("selectValue is null"); } if (!ParseJsMedia(selectIcon, icon)) { LOGI("selectIcon is null"); } params[i] = { value, icon }; } SelectModel::GetInstance()->Create(params); } } void JSSelect::JSBind(BindingTarget globalObj) { JSClass::Declare("Select"); MethodOptions opt = MethodOptions::NONE; JSClass::StaticMethod("create", &JSSelect::Create, opt); JSClass::StaticMethod("selected", &JSSelect::Selected, opt); JSClass::StaticMethod("value", &JSSelect::Value, opt); JSClass::StaticMethod("font", &JSSelect::Font, opt); JSClass::StaticMethod("fontColor", &JSSelect::FontColor, opt); JSClass::StaticMethod("selectedOptionBgColor", &JSSelect::SelectedOptionBgColor, opt); JSClass::StaticMethod("selectedOptionFont", &JSSelect::SelectedOptionFont, opt); JSClass::StaticMethod("selectedOptionFontColor", &JSSelect::SelectedOptionFontColor, opt); JSClass::StaticMethod("optionBgColor", &JSSelect::OptionBgColor, opt); JSClass::StaticMethod("optionFont", &JSSelect::OptionFont, opt); JSClass::StaticMethod("optionFontColor", &JSSelect::OptionFontColor, opt); JSClass::StaticMethod("onSelect", &JSSelect::OnSelected, opt); JSClass::StaticMethod("space", &JSSelect::SetSpace, opt); JSClass::StaticMethod("arrowPosition", &JSSelect::SetArrowPosition, opt); JSClass::StaticMethod("menuAlign", &JSSelect::SetMenuAlign, opt); // API7 onSelected deprecated JSClass::StaticMethod("onSelected", &JSSelect::OnSelected, opt); JSClass::StaticMethod("width", &JSSelect::JsWidth); JSClass::StaticMethod("height", &JSSelect::JsHeight); JSClass::StaticMethod("size", &JSSelect::JsSize); JSClass::StaticMethod("padding", &JSSelect::JsPadding); JSClass::StaticMethod("paddingTop", &JSSelect::SetPaddingTop, opt); JSClass::StaticMethod("paddingBottom", &JSSelect::SetPaddingBottom, opt); JSClass::StaticMethod("paddingLeft", &JSSelect::SetPaddingLeft, opt); JSClass::StaticMethod("paddingRight", &JSSelect::SetPaddingRight, opt); JSClass::StaticMethod("onClick", &JSInteractableView::JsOnClick); JSClass::StaticMethod("onTouch", &JSInteractableView::JsOnTouch); JSClass::StaticMethod("onKeyEvent", &JSInteractableView::JsOnKey); JSClass::StaticMethod("onDeleteEvent", &JSInteractableView::JsOnDelete); JSClass::StaticMethod("onAppear", &JSInteractableView::JsOnAppear); JSClass::StaticMethod("onDisAppear", &JSInteractableView::JsOnDisAppear); JSClass::InheritAndBind(globalObj); } void ParseSelectedObject(const JSCallbackInfo& info, const JSRef& changeEventVal) { CHECK_NULL_VOID(changeEventVal->IsFunction()); auto jsFunc = AceType::MakeRefPtr(JSRef(), JSRef::Cast(changeEventVal)); auto onSelect = [execCtx = info.GetExecutionContext(), func = std::move(jsFunc)](int32_t index) { JAVASCRIPT_EXECUTION_SCOPE_WITH_CHECK(execCtx); ACE_SCORING_EVENT("Select.SelectChangeEvent"); auto newJSVal = JSRef::Make(ToJSValue(index)); func->ExecuteJS(1, &newJSVal); }; SelectModel::GetInstance()->SetSelectChangeEvent(onSelect); } void JSSelect::Selected(const JSCallbackInfo& info) { if (info.Length() < 1 || info.Length() > 2) { LOGE("The arg is wrong, it is supposed to have 1 or 2 arguments"); return; } int32_t value = 0; if (info.Length() > 0 && info[0]->IsNumber()) { value = info[0]->ToNumber(); } if (value < -1) { value = -1; } if (info.Length() > 1 && info[1]->IsFunction()) { ParseSelectedObject(info, info[1]); } SelectModel::GetInstance()->SetSelected(value); } void ParseValueObject(const JSCallbackInfo& info, const JSRef& changeEventVal) { CHECK_NULL_VOID(changeEventVal->IsFunction()); auto jsFunc = AceType::MakeRefPtr(JSRef(), JSRef::Cast(changeEventVal)); auto onSelect = [execCtx = info.GetExecutionContext(), func = std::move(jsFunc)](const std::string& value) { JAVASCRIPT_EXECUTION_SCOPE_WITH_CHECK(execCtx); ACE_SCORING_EVENT("Select.ValueChangeEvent"); auto newJSVal = JSRef::Make(ToJSValue(value)); func->ExecuteJS(1, &newJSVal); }; SelectModel::GetInstance()->SetValueChangeEvent(onSelect); } void JSSelect::Value(const JSCallbackInfo& info) { if (info.Length() < 1 || info.Length() > 2) { LOGE("The arg is wrong, it is supposed to have 1 or 2 arguments"); return; } std::string value; if (info.Length() > 0 && info[0]->IsString()) { value = info[0]->ToString(); } if (info.Length() > 1 && info[1]->IsFunction()) { ParseValueObject(info, info[1]); } SelectModel::GetInstance()->SetValue(value); } void JSSelect::Font(const JSCallbackInfo& info) { if (!info[0]->IsObject()) { return; } auto param = JSRef::Cast(info[0]); auto size = param->GetProperty("size"); if (!size->IsNull()) { CalcDimension fontSize; if (ParseJsDimensionFp(size, fontSize)) { SelectModel::GetInstance()->SetFontSize(fontSize); } } std::string weight; auto fontWeight = param->GetProperty("weight"); if (!fontWeight->IsNull()) { if (fontWeight->IsNumber()) { weight = std::to_string(fontWeight->ToNumber()); } else { ParseJsString(fontWeight, weight); } SelectModel::GetInstance()->SetFontWeight(ConvertStrToFontWeight(weight)); } auto family = param->GetProperty("family"); if (!family->IsNull() && family->IsString()) { auto familyVal = family->ToString(); SelectModel::GetInstance()->SetFontFamily(ConvertStrToFontFamilies(familyVal)); } auto style = param->GetProperty("style"); if (!style->IsNull() && style->IsNumber()) { auto styleVal = static_cast(style->ToNumber()); SelectModel::GetInstance()->SetItalicFontStyle(styleVal); } } void JSSelect::FontColor(const JSCallbackInfo& info) { if (info.Length() < 1) { LOGE("The argv is wrong, it is supposed to have at least 1 argument"); return; } Color textColor; if (!ParseJsColor(info[0], textColor)) { if (info[0]->IsNull() || info[0]->IsUndefined()) { auto pipeline = PipelineBase::GetCurrentContext(); CHECK_NULL_VOID_NOLOG(pipeline); auto theme = pipeline->GetTheme(); CHECK_NULL_VOID_NOLOG(theme); textColor = theme->GetFontColor(); } else { return; } } SelectModel::GetInstance()->SetFontColor(textColor); } void JSSelect::SelectedOptionBgColor(const JSCallbackInfo& info) { if (info.Length() < 1) { LOGE("The argv is wrong, it is supposed to have at least 1 argument"); return; } Color bgColor; if (!ParseJsColor(info[0], bgColor)) { if (info[0]->IsUndefined() || info[0]->IsNull()) { auto pipeline = PipelineBase::GetCurrentContext(); CHECK_NULL_VOID_NOLOG(pipeline); auto theme = pipeline->GetTheme(); CHECK_NULL_VOID_NOLOG(theme); bgColor = theme->GetSelectedColor(); } else { return; } } SelectModel::GetInstance()->SetSelectedOptionBgColor(bgColor); } void JSSelect::SelectedOptionFont(const JSCallbackInfo& info) { if (!info[0]->IsObject()) { return; } auto param = JSRef::Cast(info[0]); if (info.Length() < 1) { LOGE("The argv is wrong, it is supposed to have at least 1 argument"); return; } auto size = param->GetProperty("size"); if (!size->IsNull()) { CalcDimension fontSize; if (ParseJsDimensionFp(size, fontSize)) { SelectModel::GetInstance()->SetSelectedOptionFontSize(fontSize); } else if (size->IsUndefined()) { auto pipeline = PipelineBase::GetCurrentContext(); CHECK_NULL_VOID_NOLOG(pipeline); auto theme = pipeline->GetTheme(); CHECK_NULL_VOID_NOLOG(theme); SelectModel::GetInstance()->SetSelectedOptionFontSize(theme->GetFontSize()); } } std::string weight; auto fontWeight = param->GetProperty("weight"); if (!fontWeight->IsNull()) { if (fontWeight->IsNumber()) { weight = std::to_string(fontWeight->ToNumber()); } else { ParseJsString(fontWeight, weight); } SelectModel::GetInstance()->SetSelectedOptionFontWeight(ConvertStrToFontWeight(weight)); } auto family = param->GetProperty("family"); if (!family->IsNull() && family->IsString()) { auto familyVal = family->ToString(); SelectModel::GetInstance()->SetSelectedOptionFontFamily(ConvertStrToFontFamilies(familyVal)); } auto style = param->GetProperty("style"); if (!style->IsNull() && style->IsNumber()) { auto styleVal = static_cast(style->ToNumber()); SelectModel::GetInstance()->SetSelectedOptionItalicFontStyle(styleVal); } } void JSSelect::SelectedOptionFontColor(const JSCallbackInfo& info) { if (info.Length() < 1) { LOGE("The argv is wrong, it is supposed to have at least 1 argument"); return; } Color textColor; if (!ParseJsColor(info[0], textColor)) { if (info[0]->IsNull() || info[0]->IsUndefined()) { auto pipeline = PipelineBase::GetCurrentContext(); CHECK_NULL_VOID_NOLOG(pipeline); auto theme = pipeline->GetTheme(); CHECK_NULL_VOID_NOLOG(theme); textColor = theme->GetSelectedColorText(); } else { return; } } SelectModel::GetInstance()->SetSelectedOptionFontColor(textColor); } void JSSelect::OptionBgColor(const JSCallbackInfo& info) { if (info.Length() < 1) { LOGE("The argv is wrong, it is supposed to have at least 1 argument"); return; } Color bgColor; if (!ParseJsColor(info[0], bgColor)) { return; } SelectModel::GetInstance()->SetOptionBgColor(bgColor); } void JSSelect::OptionFont(const JSCallbackInfo& info) { if (!info[0]->IsObject()) { return; } auto param = JSRef::Cast(info[0]); auto size = param->GetProperty("size"); if (!size->IsNull()) { CalcDimension fontSize; if (ParseJsDimensionFp(size, fontSize)) { SelectModel::GetInstance()->SetOptionFontSize(fontSize); } if (size->IsUndefined()) { auto pipeline = PipelineBase::GetCurrentContext(); CHECK_NULL_VOID_NOLOG(pipeline); auto theme = pipeline->GetTheme(); CHECK_NULL_VOID_NOLOG(theme); SelectModel::GetInstance()->SetOptionFontSize(theme->GetFontSize()); } } std::string weight; auto fontWeight = param->GetProperty("weight"); if (!fontWeight->IsNull()) { if (fontWeight->IsNumber()) { weight = std::to_string(fontWeight->ToNumber()); } else { ParseJsString(fontWeight, weight); } SelectModel::GetInstance()->SetOptionFontWeight(ConvertStrToFontWeight(weight)); } auto family = param->GetProperty("family"); if (!family->IsNull() && family->IsString()) { auto familyVal = family->ToString(); SelectModel::GetInstance()->SetOptionFontFamily(ConvertStrToFontFamilies(familyVal)); } auto style = param->GetProperty("style"); if (!style->IsNull() && style->IsNumber()) { auto styleVal = static_cast(style->ToNumber()); SelectModel::GetInstance()->SetOptionItalicFontStyle(styleVal); } } void JSSelect::OptionFontColor(const JSCallbackInfo& info) { if (info.Length() < 1) { LOGE("The argv is wrong, it is supposed to have at least 1 argument"); return; } Color textColor; if (!ParseJsColor(info[0], textColor)) { return; } SelectModel::GetInstance()->SetOptionFontColor(textColor); } void JSSelect::OnSelected(const JSCallbackInfo& info) { if (!info[0]->IsFunction()) { LOGE("info[0] is not a function."); return; } auto jsFunc = AceType::MakeRefPtr(JSRef(), JSRef::Cast(info[0])); auto onSelect = [execCtx = info.GetExecutionContext(), func = std::move(jsFunc)]( int32_t index, const std::string& value) { JAVASCRIPT_EXECUTION_SCOPE_WITH_CHECK(execCtx); ACE_SCORING_EVENT("Select.onSelect"); JSRef params[2]; params[0] = JSRef::Make(ToJSValue(index)); params[1] = JSRef::Make(ToJSValue(value)); func->ExecuteJS(2, params); }; SelectModel::GetInstance()->SetOnSelect(std::move(onSelect)); info.ReturnSelf(); } void JSSelect::JsWidth(const JSCallbackInfo& info) { if (info.Length() < 1) { LOGE("The arg is wrong, it is supposed to have atleast 1 arguments"); return; } CalcDimension value; if (!ParseJsDimensionVp(info[0], value)) { return; } SelectModel::GetInstance()->SetWidth(value); } void JSSelect::JsHeight(const JSCallbackInfo& info) { if (info.Length() < 1) { LOGE("The arg is wrong, it is supposed to have atleast 1 arguments"); return; } CalcDimension value; if (!ParseJsDimensionVp(info[0], value)) { return; } SelectModel::GetInstance()->SetHeight(value); } bool CheckJSCallbackInfo( const std::string& callerName, const JSCallbackInfo& info, std::vector& infoTypes) { if (info.Length() < 1) { LOGE("%{public}s: The arg is supposed to have at least one argument", callerName.c_str()); return false; } bool typeVerified = false; std::string unrecognizedType; for (const auto& infoType : infoTypes) { switch (infoType) { case JSCallbackInfoType::STRING: if (info[0]->IsString()) { typeVerified = true; } else { unrecognizedType += "string|"; } break; case JSCallbackInfoType::NUMBER: if (info[0]->IsNumber()) { typeVerified = true; } else { unrecognizedType += "number|"; } break; case JSCallbackInfoType::OBJECT: if (info[0]->IsObject()) { typeVerified = true; } else { unrecognizedType += "object|"; } break; case JSCallbackInfoType::FUNCTION: if (info[0]->IsFunction()) { typeVerified = true; } else { unrecognizedType += "Function|"; } break; default: break; } } if (!typeVerified) { LOGE("%{public}s: info[0] is not a [%{public}s]", callerName.c_str(), unrecognizedType.substr(0, unrecognizedType.size() - 1).c_str()); } return typeVerified || infoTypes.size() == 0; } void JSSelect::JsSize(const JSCallbackInfo& info) { std::vector checkList { JSCallbackInfoType::OBJECT }; if (!CheckJSCallbackInfo("JsSize", info, checkList)) { return; } JSRef sizeObj = JSRef::Cast(info[0]); CalcDimension width; if (!ParseJsDimensionVp(sizeObj->GetProperty("width"), width)) { return; } CalcDimension height; if (!ParseJsDimensionVp(sizeObj->GetProperty("height"), height)) { return; } SelectModel::GetInstance()->SetSize(width, height); } void JSSelect::JsPadding(const JSCallbackInfo& info) { if (!info[0]->IsString() && !info[0]->IsNumber() && !info[0]->IsObject()) { LOGE("arg is not a string, number or object."); return; } if (info[0]->IsObject()) { std::optional left; std::optional right; std::optional top; std::optional bottom; JSRef paddingObj = JSRef::Cast(info[0]); CalcDimension leftDimen; if (ParseJsDimensionVp(paddingObj->GetProperty("left"), leftDimen)) { left = leftDimen; } CalcDimension rightDimen; if (ParseJsDimensionVp(paddingObj->GetProperty("right"), rightDimen)) { right = rightDimen; } CalcDimension topDimen; if (ParseJsDimensionVp(paddingObj->GetProperty("top"), topDimen)) { top = topDimen; } CalcDimension bottomDimen; if (ParseJsDimensionVp(paddingObj->GetProperty("bottom"), bottomDimen)) { bottom = bottomDimen; } if (left.has_value() || right.has_value() || top.has_value() || bottom.has_value()) { ViewAbstractModel::GetInstance()->SetPaddings(top, bottom, left, right); return; } } CalcDimension value; if (!ParseJsDimensionVp(info[0], value)) { value.Reset(); } SelectModel::GetInstance()->SetPadding(value); } void JSSelect::SetPaddingLeft(const JSCallbackInfo& info) { if (info.Length() < 1) { LOGE("The arg is wrong, it is supposed to have at least 1 argument"); return; } CalcDimension value; if (!ParseJsDimensionVp(info[0], value)) { return; } SelectModel::GetInstance()->SetPaddingLeft(value); } void JSSelect::SetPaddingTop(const JSCallbackInfo& info) { if (info.Length() < 1) { LOGE("The arg is wrong, it is supposed to have at least 1 argument"); return; } CalcDimension value; if (!ParseJsDimensionVp(info[0], value)) { return; } SelectModel::GetInstance()->SetPaddingTop(value); } void JSSelect::SetPaddingRight(const JSCallbackInfo& info) { if (info.Length() < 1) { LOGE("The arg is wrong, it is supposed to have at least 1 argument"); return; } CalcDimension value; if (!ParseJsDimensionVp(info[0], value)) { return; } SelectModel::GetInstance()->SetPaddingRight(value); } void JSSelect::SetPaddingBottom(const JSCallbackInfo& info) { if (info.Length() < 1) { LOGE("The arg is wrong, it is supposed to have at least 1 argument"); return; } CalcDimension value; if (!ParseJsDimensionVp(info[0], value)) { return; } SelectModel::GetInstance()->SetPaddingBottom(value); } void JSSelect::SetSpace(const JSCallbackInfo& info) { if (info.Length() < 1) { LOGI("The arg is wrong, it is supposed to have at least 1 argument"); return; } auto selectTheme = GetTheme(); CalcDimension value; if (!ParseJsDimensionVp(info[0], value)) { LOGI("JSSelect set space value is mull"); value = selectTheme->GetContentSpinnerPadding(); } if (LessNotEqual(value.Value(), 0.0) || value.Unit() == DimensionUnit::PERCENT) { LOGI("JSSelect set space value is to small"); value = selectTheme->GetContentSpinnerPadding(); } SelectModel::GetInstance()->SetSpace(value); } void JSSelect::SetArrowPosition(const JSCallbackInfo& info) { if (info.Length() < 1) { return; } int32_t direction = 0; if (!ParseJsInt32(info[0], direction)) { direction = 0; } if (static_cast(direction) != ArrowPosition::START && static_cast(direction) != ArrowPosition::END) { direction = 0; } SelectModel::GetInstance()->SetArrowPosition(static_cast(direction)); } void JSSelect::SetMenuAlign(const JSCallbackInfo& info) { if (info.Length() < 1) { return; } if (!info[0]->IsNumber()) { return; } MenuAlign menuAlignObj; menuAlignObj.alignType = static_cast(info[0]->ToNumber()); if (info.Length() > 1) { if (!info[1]->IsObject()) { return; } auto offsetObj = JSRef::Cast(info[1]); CalcDimension dx; auto dxValue = offsetObj->GetProperty("dx"); ParseJsDimensionVp(dxValue, dx); CalcDimension dy; auto dyValue = offsetObj->GetProperty("dy"); ParseJsDimensionVp(dyValue, dy); menuAlignObj.offset = DimensionOffset(dx, dy); } SelectModel::GetInstance()->SetMenuAlign(menuAlignObj); } } // namespace OHOS::Ace::Framework