LWC1079 Ожидаемый корневой тег будет шаблоном, найден мета
Нашел этот код в Интернете как открытый исходный код, и я пытался преобразовать его в LWC. Код находится в HTML, CSS и JS. Я работаю над этим в визуальной студии и использую пакет расширений salesforce, который не принимает HTML, ему нужны теги, но я никогда раньше не использовал теги шаблонов. Это также дает мне ошибку: метатег не разрешен. Я понятия не имею, что это за ошибка. Кто-нибудь может помочь? Ошибка в строке 3
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=yes" />
<title>Maths Game</title>
<link rel="stylesheet" href="mathGame.css" />
</head>
<body>
<main>
<div id="container">
<p id="message" class="structure-elements"></p>
<aside id="score" class="structure-elements">Score: <span>00</span></aside>
<div id="calculation">
<section id="question" class="structure-elements"></section>
<p id="instruction" class="structure-elements">Click on the correct answer</p>
<ul id="choices" class="structure-elements">
<li></li>
<li></li>
<li></li>
<li></li>
</ul>
</div>
<button id="start-reset" class="structure-elements">Start Game</button>
<aside id="time-remaining" class="structure-elements">Time remaining: <span>60</span> sec</aside>
</div>
<div id="game-over" class="structure-elements">
<p>Game over!</p>
<p>Your score is <span>00</span>.</p>
</div>
</main>
<script src="mathGame.js"></script>
</body>
</html>
**Это часть кода Javascript.
var Counter = {
PlayingState: null,
IsStoped: true,
Score: 0,
TimeRemaining: 0,
FirstNumber: 0,
SecondNumber: 0,
CorrectAnswer: 0,
CorrectPosition: 0,
WrongAnswer: 0,
AddContentToElement: function(selector, content)
{
document.querySelector(selector).innerHTML = content;
},
ChangeStyle: function(selector, property, value)
{
document.querySelector(selector).setAttribute(property, value);
},
Initialize: function(timeRemaining)
{
this.TimeRemaining = timeRemaining;
},
GenerateRandomNumber: function(multiplier)
{
return Math.round( Math.random() * multiplier ) + 1;
},
Refresh: function(selector, data)
{
document.querySelector(selector).innerText = (data < 10 ? "0" : "") + data;
},
LoopThroughElements: function()
{
var answers = [this.CorrectAnswer];
for (var index = 1; index < 5; index++)
{
this.ChangeStyle("ul#choices > li:nth-of-type(" + index + ")", "style", "height:auto;");
if (index !== this.CorrectPosition)
{
do
{
this.WrongAnswer = this.GenerateRandomNumber(9) * this.GenerateRandomNumber(9);
} while ( answers.indexOf(this.WrongAnswer) > -1 );
this.AddContentToElement( "ul#choices > li:nth-of-type(" + index + ")", this.WrongAnswer );
answers.push(this.WrongAnswer);
}
}
},
Launch: function()
{
this.IsStoped = false;
this.Action();
this.ChangeStyle("aside#time-remaining", "style", "visibility:visible;");
this.ChangeStyle("#game-over", "style", "display:none;");
this.ChangeStyle("ul#choices", "style", "pointer-events:initial; opacity:1;");
this.ChangeStyle("button#start-reset", "style", "visibility:hidden;");
this.AddContentToElement("button#start-reset", "Reset Game");
this.Refresh("aside#time-remaining > span", this.TimeRemaining);
this.GenerateQuestionAndAnswers();
},
GenerateQuestionAndAnswers: function()
{
this.FirstNumber = this.GenerateRandomNumber(9);
this.SecondNumber = this.GenerateRandomNumber(9);
this.CorrectAnswer = this.FirstNumber * this.SecondNumber;
this.CorrectPosition = this.GenerateRandomNumber(3);
this.ChangeStyle("section#question", "style", "height:auto;");
this.AddContentToElement("section#question", this.FirstNumber + "x" + this.SecondNumber);
this.AddContentToElement( "ul#choices > li:nth-of-type(" + this.CorrectPosition + ")", this.CorrectAnswer );
this.LoopThroughElements();
},
Action: function()
{
Counter.PlayingState = setInterval( function()
{
Counter.TimeRemaining--;
if (Counter.TimeRemaining <= 50)
{
Counter.ChangeStyle("button#start-reset", "style", "visibility:visible;");
}
if (Counter.TimeRemaining < 1)
{
Counter.Stop();
}
else
{
Counter.Refresh("aside#time-remaining > span", Counter.TimeRemaining);
}
}, 1000 );
},
EventListener: function(event)
{
if ( Number(event.currentTarget.innerText) === Number(Counter.CorrectAnswer) )
{
Counter.Score++;
Counter.Refresh("aside#score > span", Counter.Score);
Counter.GenerateQuestionAndAnswers();
Counter.ChangeStyle("p#message", "style", "visibility:visible; background-color:#23A230;");
Counter.AddContentToElement("p#message", "Correct");
}
else
{
if (Counter.Score >= 1)
{
Counter.Score -= 0.5;
Counter.Refresh("aside#score > span", Counter.Score);
}
Counter.ChangeStyle("p#message", "style", "visibility:visible; background-color:#DE401A;");
Counter.AddContentToElement("p#message", "Try again");
}
setTimeout( function()
{
Counter.ChangeStyle("p#message", "style", "visibility:hidden;");
}, 1000 );
},
CheckClickOnRightAnswer: function()
{
for (var index = 1; index < 5; index++)
{
document.querySelector("ul#choices > li:nth-of-type(" + index + ")").removeEventListener("click", this.EventListener, false);
document.querySelector("ul#choices > li:nth-of-type(" + index + ")").addEventListener("click", this.EventListener);
}
},
Stop: function()
{
this.IsStoped = true;
clearInterval(this.PlayingState);
this.ChangeStyle("ul#choices", "style", "pointer-events:none; opacity:0.4;");
this.ChangeStyle("aside#time-remaining", "style", "visibility:hidden;");
this.ChangeStyle("div#game-over", "style", "display:block;");
this.AddContentToElement("button#start-reset", "Start Game");
this.AddContentToElement( "div#game-over > p:last-of-type > span", (this.Score !== "00" && this.Score < 10 ? "0" : "") + this.Score );
this.AddContentToElement("aside#score > span", this.Score = "00");
}
};
/*************************************************************************************************/
/* ************************************** CODE PRINCIPAL *************************************** */
/*************************************************************************************************/
document.addEventListener('DOMContentLoaded', function()
{
document.getElementById("start-reset").addEventListener("click", function()
{
Counter.Initialize(60);
Counter.IsStoped ? Counter.Launch() : Counter.Stop();
Counter.CheckClickOnRightAnswer();
});
});
1 ответ
С LWC вы не пишете полностраничные приложения, нет <html>, <head>, <body>
. Вы пишете небольшие повторно используемые компоненты с <template>
тег, и их можно размещать на разных страницах. И большую часть времени вы не манипулируете HTML напрямую. Вы устанавливаете значения для переменных JS, и фреймворк повторно отображает соответствующие части. Упрощает разделение представления и логики и делает логику тестируемой (да, для LWC могут быть модульные тесты).
Эти индивидуальные тренинги могут быть полезны: https://trailhead.salesforce.com/content/learn/modules/modern-javascript-development, https://trailhead.salesforce.com/en/content/learn/modules/lightning-web-components-basics
Итак... Если вы просто хотите, чтобы это работало в Salesforce, вы всегда можете сделать из него страницу Visualforce, которая ближе всего к полностраничному приложению. Или загрузите свой материал как статический ресурс, а затем используйте ligthning:container
Компонент Aura для его загрузки. Он будет загружаться как iframe, но стили не будут конфликтовать, для переноса приложения требуются минимальные знания JS, иногда это так. Немного более "за" было бы попытаться переписать его как можно реже. Эта вещь сильно манипулирует необработанным HTML, и это не совсем способ LWC, но это возможно с lwc:dom="manual"
Если любите приключения - перепишите его на LWC. Это далеко не идеально и не полностью переписано, но должно дать вам некоторые идеи.
html
<template>
<div>
<lightning-layout multiple-rows="true">
<lightning-layout-item size="6"><span class={messageStyle}>{message}</span></lightning-layout-item>
<lightning-layout-item size="6">
Score:
<lightning-formatted-number value={score} minimum-integer-digits="2"></lightning-formatted-number>
</lightning-layout-item>
<template if:false={isStopped}>
<lightning-layout-item size="12"><span class="question">{question}</span></lightning-layout-item>
<lightning-layout-item size="12">Click on the correct answer</lightning-layout-item>
<template for:each={answers} for:item="a">
<lightning-layout-item key={a.value} size="3">
<lightning-button label={a.value} value={a.value} variant="neutral" onclick={handleAnswerClick}></lightning-button>
</lightning-layout-item>
</template>
</template>
<lightning-layout-item size="6">
<template if:true={isButtonVisible}>
<lightning-button label={buttonLabel} variant={buttonVariant} onclick={handleButtonClick}></lightning-button>
</template>
</lightning-layout-item>
<lightning-layout-item size="6">
Time remaining:
<lightning-formatted-number value={timeRemaining} minimum-integer-digits="2"></lightning-formatted-number>
</lightning-layout-item>
</lightning-layout>
</div>
</template>
js
import { track, LightningElement } from 'lwc';
import { ShowToastEvent } from 'lightning/platformShowToastEvent'
export default class Stack63257378 extends LightningElement {
buttonLabel = 'Start Game';
buttonVariant = 'success';
isButtonVisible = true;
message;
messageStyle;
firstNumber;
secondNumber;
@track answers = [];
timeRemaining;
score;
isStopped = true;
handleButtonClick(event){
this.isStopped ? this.launch() : this.stop();
}
launch(){
this.timeRemaining = 15; // 60
this.isStopped = this.isButtonVisible = false;
this.score = 0;
let interval = setInterval(function (){
--this.timeRemaining;
if(this.timeRemaining <= 10){ // 50
this.isButtonVisible = true;
this.buttonLabel = 'Stop Game';
this.buttonVariant = 'destructive';
}
if(this.timeRemaining < 1){
clearInterval(interval);
this.stop();
}
}.bind(this),1000);
this.generateQuestion();
}
handleAnswerClick(event){
if(this.correctAnswer === event.target.value){
++this.score;
this.generateQuestion();
this.message = 'Correct';
this.messageStyle = 'good';
} else {
if(this.score >= 1) {
this.score -= 0.5;
}
this.message = 'Try again';
this.messageStyle = 'bad';
}
}
stop(){
this.answers = [];
this.isStopped = true;
this.buttonLabel = 'Start Game';
this.buttonVariant = 'success';
this.message = this.messageStyle = null;
const event = new ShowToastEvent({
title: 'Game over!',
message: `Your score is ${this.score}`,
});
this.dispatchEvent(event);
}
generateQuestion(){
this.firstNumber = this.getRandomNumber(9);
this.secondNumber = this.getRandomNumber(9);
this.correctAnswer = this.firstNumber * this.secondNumber;
this.answers = [];
let correctPosition = this.getRandomNumber(3);
for(let i = 0; i < 4; ++i){
let obj = {"i" : i, "value" : i === correctPosition ? this.correctAnswer : this.getRandomNumber(9) * this.getRandomNumber(9)};
this.answers.push(obj);
}
}
getRandomNumber(range){
return Math.round( Math.random() * range ) + 1
}
get question(){
return this.isStopped ? '' : `${this.firstNumber} x ${this.secondNumber}`;
}
}
css
.good {
background-color:#23A230;
}
.bad {
background-color:#DE401A;
}
.question {
font-size: 24px;
}
button {
width: 100%;
}
"meta-xml" (решает, где вы можете встроить этот компонент в SF)
<?xml version="1.0" encoding="UTF-8"?>
<LightningComponentBundle xmlns="http://soap.sforce.com/2006/04/metadata">
<apiVersion>48.0</apiVersion>
<isExposed>true</isExposed>
<masterLabel>Math game</masterLabel>
<description>https://stackru.com/q/63257378/313628</description>
<targets>
<target>lightning__AppPage</target>
<target>lightning__HomePage</target>
<target>lightning__RecordPage</target>
</targets>
</LightningComponentBundle>