1.static resource를 사용하는 방법은 간단한데
setup 부분에서 static을 검색한 다음 컴퓨터에 있는 파일을 등록하고(이름 중요)
아래의 코드처럼 js에서 해당 파일을 import 하면 된다.
주소 자체에 해당 파일명이 들어가기 때문에 명확한 이름이 유리하다.
//html
<template>
<lightning-card title="Toast Events" icon-name="utility:animal_and_nature">
<div class="slds-var-m-around_medium">
<div style="padding: 10px">
<img src={logo1url}>
<img src={logo2url}>
</div>
</div>
</lightning-card>
</template>
//js
import { LightningElement } from "lwc";
import logo1 from "@salesforce/resourceUrl/logo1";
import logo2 from "@salesforce/resourceUrl/logo2";
export default class StaticResource extends LightningElement {
logo1url = logo1;
logo2url = logo2;
}
2.permission에 따라 보이는 내용을 다르게 할 수 있는데
setup에서 permission을 설정한 다음 해당 permission을 보유했는지 조회할 수 있다.
해당 permission들은 permission sets를 통해 편하게 부여할 수 있고
hasAccessUI라는 명령어를 통해 해당 permission의 주소를 입력할 경우 알아서 boolean type으로
값을 반환해주기 때문에 template의 if:true, if:false를 사용하여 서로 다른 내용을 보여줄 수 있다.
//html
<template>
<lightning-card title="Permission Set UI" icon-name="utility:animal_and_nature">
<div class="slds-var-m-around_medium">
<div style="padding: 10px">
<template if:true={isUIAccessible}>
<h1>I am available.</h1>
</template>
<template if:false={isUIAccessible}>
<h1>I am not available.</h1>
</template>
</div>
</div>
</lightning-card>
</template>
//js
import { LightningElement } from "lwc";
import hasAccessUI from "@salesforce/customPermission/lwcStack";
export default class PermissionSetUI extends LightningElement {
get isUIAccessible() {
return hasAccessUI;
}
}
3.Navigation 기능은 여기저기에서 많이 쓸 것 같은 유용한 기능인데
Action이나 state를 추가해 생성, 초기값, 필터링 등 유용한 기능들이 많이 있다.
주의사항은 extends에 NavigationMixin를 넣어줘야 한다는 것으로
이걸 빼고 작동이 되지 않아 당황했었다.
//html
<template>
<lightning-card title="Navigate" icon-name="utility:animal_and_nature">
<div class="slds-var-m-around_medium">
<lightning-button label="Navigate to Home" class="slds-var-m-around_medium"
onclick={goHome}></lightning-button>
<lightning-button label="Navigate to New Contact" class="slds-var-m-around_medium"
onclick={goNewContact}></lightning-button>
<lightning-button label="Navigate to Nw Contact with value" class="slds-var-m-around_medium"
onclick={goNewContactWithValue}></lightning-button>
<lightning-button label="Navigate to Contact Listview" class="slds-var-m-around_medium"
onclick={goContactList}></lightning-button>
<lightning-button label="Navigate to Tab" class="slds-var-m-around_medium"
onclick={goTab}></lightning-button>
</div>
</lightning-card>
</template>
//js
import { LightningElement } from "lwc";
import { NavigationMixin } from "lightning/navigation";
import { encodeDefaultFieldValues } from "lightning/pageReferenceUtils";
export default class Navigations extends NavigationMixin(LightningElement) {
goHome() {
this[NavigationMixin.Navigate]({
type: "standard__namedPage",
attributes: {
pageName: "home"
}
});
}
goNewContact() {
this[NavigationMixin.Navigate]({
type: "standard__objectPage",
attributes: {
objectApiName: "Contact",
actionName: "new"
}
});
}
goNewContactWithValue() {
const defaultValue = encodeDefaultFieldValues({
FirstName: "Jichang",
LastName: "Ryu"
});
this[NavigationMixin.Navigate]({
type: "standard__objectPage",
attributes: {
objectApiName: "Contact",
actionName: "new"
},
state: {
defaultFieldValues: defaultValue
}
});
}
goContactList() {
this[NavigationMixin.Navigate]({
type: "standard__objectPage",
attributes: {
objectApiName: "Contact",
actionName: "list"
},
state: {
filterName: "Recent"
}
});
}
goTab() {
this[NavigationMixin.Navigate]({
type: "standard__navItemPage",
attributes: {
apiName: "LWCStack"
}
});
}
}
4.modal 부분은 코드가 너무 많고 복잡해서 작성하기는 어려울 것 같았다.
실제로 이번 강의에서는 코드를 제대로 보여주지도 않고 이론적 설명만 진행했는데
기존의 작성 코드와 스타일이 다른 것을 보면 공식문서에서 복사한 느낌이 강했다.
모달을 작성해야 할 경우 Lightning design system에 들어가서
blueprint modal 부분을 확인해야겠다.
5.quick action은 조금 재미있는 구조였는데
모달과 유사하게 lwc를 가져와서 사용하는 느낌이었다.
또한 CloseActionScreenEvent를 사용해 닫는 것도 신기했는데
보통은 상위 컴포넌트를 클릭시 위로 전파되며 닫히고
현재 컴포넌트는 내부의 취소 또는 x 버튼을 눌러야 닫히는 구조로만 생성했었는데
띄운 창을 닫는 기능 자체가 있는 것은 조금 신선했다.
적으며 코드를 다시 보니 대단한 것은 아니었고
state를 기준으로 true/false로 띄우는 것이 아니기 때문에
CloseActionScreenEvent가 없으면 닫을 수가 없는 상태라 당연한 기능이었던 것 같다.
게다가 현재도 외부를 클릭할 경우 닫히지 않는건 마찬가지인데
외부 클릭할 경우 닫히게 추가해야 하는 것도 동일하다.
//meta
<?xml version="1.0" encoding="UTF-8" ?>
<LightningComponentBundle xmlns="http://soap.sforce.com/2006/04/metadata">
<apiVersion>56.0</apiVersion>
<isExposed>true</isExposed>
<targets>
<target>lightning__RecordAction</target>
</targets>
<targetConfigs>
<targetConfig targets="lightning__RecordAction">
<actionType>ScreenAction</actionType>
</targetConfig>
</targetConfigs>
</LightningComponentBundle>
//html
<!-- sldsValidatorIgnore -->
<!-- sldsValidatorIgnoreNextLine -->
<template>
<lightning-quick-action-panel title="Quick Action Title">
Body Contents
<div slot="footer">
<lightning-button variant="brand" label="Save"></lightning-button>
<lightning-button variant="neutral" label="Cancel" onclick={closeAction}></lightning-button>
</div>
</lightning-quick-action-panel>
</template>
//js
import { LightningElement } from "lwc";
import { CloseActionScreenEvent } from "lightning/actions";
export default class QuickActionLWC extends LightningElement {
closeAction() {
this.dispatchEvent(new CloseActionScreenEvent());
}
}
추가적으로 html이 없어도 되는 quickAction도 존재했는데
단순히 js파일을 작동시키는 구조인 것 같았다.
invoke 자체가 실행 시 자동으로 작동하는 것인지
왜 저게 작동하는지는 정확하게 모르겠는데
검색을 해보니 actionName이라는 것으로 확정을 지어주지 않으면
그냥 내부에 있는 함수를 실행한다고 한다.
invoke인지 아닌지가 중요한 것이 아니고
console.log를 찍는다는 의미가 invoke 였을 뿐 다른 기능을 할 때는 changePage 등 아무 이름이나 사용해도 상관 없었다.
//meta
<?xml version="1.0" encoding="UTF-8" ?>
<LightningComponentBundle xmlns="http://soap.sforce.com/2006/04/metadata">
<apiVersion>56.0</apiVersion>
<isExposed>true</isExposed>
<targets>
<target>lightning__RecordAction</target>
</targets>
<targetConfigs>
<targetConfig targets="lightning__RecordAction">
<actionType>Action</actionType>
</targetConfig>
</targetConfigs>
</LightningComponentBundle>
//js
import { LightningElement, api } from "lwc";
export default class HeaderlessActionLWC extends LightningElement {
@api invoke() {
console.log("headerlessAction Clicked");
}
}
6.이번 코드에서는 실수가 있었는데 왜 동영상에서는 통과가 됐는지 의아한 부분이었다.
하단의 코드를 자세히 보면 template에서 if:true로 체크하는 부분이 존재하지 않는데
이렇게 되면 contacts라는 요청을 렌더링이 되는 도중에 즉각적으로 보내게 된다.
현재 this.listview에는 아무 데이터가 없는 undefined 상태임에도 불구하고
.data.record.record로 접근하려고 하면 페이지가 터저버린다.
sfdc 자체적인 문제였던건지 아니면 예전 버전에서는 문제가 없었는지는 모르겠지만
프론트쪽에서는 당연히 터져야 하는 부분이기 때문에 오히려 통과된 것이 의아했다.
자체적으로 get contacts 내부에 if문을 통해 this.listview.data가 undefined인지를 확인하고
값이 들어있는 경우 정상적인 값을 반환해주고
값이 없는 경우 null을 반환해 에러가 생기지 않도록 처리했다.
작성자가 이전에는 if:true를 자주 사용했기 때문에
그 값이 없는 경우 하단의 값 요청인 get xxx를 사용조차 하지 않아서 문제가 없었는데
이번에는 분명 에러를 발생시킬 코드를 썼는데 자기만 영상을 멈추고 수정했던건지 의아하다.
//html
<template>
<lightning-card title="wireListView" icon-name="utility:animal_and_nature">
<div class="slds-var-m-around_medium">
<div style="padding: 10px">
<template for:each={contacts} for:item="con">
<p key={con.fields.Id.value}>{con.fields.Name.value}</p>
</template>
</div>
</div>
</lightning-card>
</template>
//js
import { LightningElement, wire } from "lwc";
import { getListUi } from "lightning/uiListApi";
import CONTACT_OBJECT from "@salesforce/schema/Contact";
import NAME_FIELD from "@salesforce/schema/Contact.Name";
export default class WireListview extends LightningElement {
@wire(getListUi, {
objectApiName: CONTACT_OBJECT,
listViewApiName: "Contact_test_view",
sortBy: NAME_FIELD,
pageSize: 20
})
listview;
get contacts() {
if (this.listview.data) {
return this.listview.data.records.records;
}
return null;
}
}
7.wire를 통해 페이지의 정보를 받아오는 것은 CurrentPageReference 를 사용해서 진행했는데
해당 명령어를 통해 pageRef에 wire를 통해 값을 넘겨줬는데
사실 중요한 것은 CurrentPageReference로 현재 페이지의 데이터를 받아온다 정도인 것 같고
해당 값은 JOSN형태이기 때문에 JSON.stringify를 사용해야 한다 정도를 주의해야겠다.
//html
<template>
<lightning-card title="wire Current Page Reference" icon-name="utility:animal_and_nature">
<div class="slds-var-m-around_medium">
<div style="padding: 10px">
{currentPageReference}
</div>
</div>
</lightning-card>
</template>
//js
import { LightningElement, wire } from "lwc";
import { CurrentPageReference } from "lightning/navigation";
export default class WirePageReference extends LightningElement {
@wire(CurrentPageReference) pageRef;
get currentPageReference() {
return this.pageRef ? JSON.stringify(this.pageRef) : "";
}
}
8.이번 코드에서는 사실 html은 큰 의미는 없고 js 부분이 중요한데
각각의 페이지들을 하나의 폴더에 몰아넣고 이름을 마음대로 정한 다음
기본 렌더링 페이지를 1로 설정했는데 render()의 역할이 아주 중요했다.
기본적으로 render()는 상태가 변할 경우 자동으로 실행되기 때문에
처음 showTemplate에 1을 할당한 순간 render가 실행되고
내부 조건에 따라 page 1,2,3을 출력하게 된다.
show1, 2, 3 또한 해당 showTemplate을 1,2,3으로 변경하는 역할이 전부였는데
이러한 변경 동작을 render()가 감지하기 때문에
단순히 상태 변경 작업만 하더라도 자동으로 페이지가 변경된다.
//html1
<template>
<lightning-card title="wire Current Page Reference" icon-name="utility:animal_and_nature">
<div class="slds-var-m-around_medium">
<p> page1 </p>
<p class="slds-var-m-vertical_small">
<lightning-button label="template1" onclick={show1}></lightning-button>
<lightning-button label="template2" onclick={show2}></lightning-button>
<lightning-button label="template3" onclick={show3}></lightning-button>
</p>
</div>
</lightning-card>
</template>
//html2
<template>
<lightning-card title="wire Current Page Reference" icon-name="utility:animal_and_nature">
<div class="slds-var-m-around_medium">
<p> page2 </p>
<p class="slds-var-m-vertical_small">
<lightning-button label="template1" onclick={show1}></lightning-button>
<lightning-button label="template2" onclick={show2}></lightning-button>
<lightning-button label="template3" onclick={show3}></lightning-button>
</p>
</div>
</lightning-card>
</template>
//html3
<template>
<lightning-card title="wire Current Page Reference" icon-name="utility:animal_and_nature">
<div class="slds-var-m-around_medium">
<p> page3 </p>
<p class="slds-var-m-vertical_small">
<lightning-button label="template1" onclick={show1}></lightning-button>
<lightning-button label="template2" onclick={show2}></lightning-button>
<lightning-button label="template3" onclick={show3}></lightning-button>
</p>
</div>
</lightning-card>
</template>
//js
import { LightningElement } from "lwc";
import template1 from "./template1.html";
import template2 from "./template2.html";
import template3 from "./template3.html";
export default class SwitchTemplates extends LightningElement {
showTemplate = "1";
render() {
if (this.showTemplate === "1") return template1;
else if (this.showTemplate === "2") return template2;
else if (this.showTemplate === "3") return template3;
return null;
}
show1() {
this.showTemplate = "1";
}
show2() {
this.showTemplate = "2";
}
show3() {
this.showTemplate = "3";
}
}
9.이번에는 유효성검사를 한다고 이상한 짓을 했는데
reportValidity와 checkValidity가 기본 html 제공 메서드라는 사실과
유효성이 어떻게 체크는 된다를 제외하면 엉망 진창인 것 같았다.
특히 답답한 부분은 유효성 검사 조건은 없었기 때문에
phone 부분에 한글이나 영어를 넣어도 문제가 없었고
이름 부분에도 숫자가 그냥 다 들어갔다.
편견없는 세일즈포스는 멋있을 수 있지만
실제 사용에서는 오타나 잘못된 입력을 막지 못하기 때문에 별로였다.
이번 코드에서 주목할 점은 조건(은 없어 보이지만)에 따른 아이디 생성과
생성 결과에 toast 반응하는 정도만 기존의 사용과 조금 다르다는 정도인 것 같다.
//html
<template>
<lightning-card title="Validate custom LWC" icon-name="utility:animal_and_nature">
<div class="slds-var-m-around_medium">
<div style="padding: 10px">
<lightning-input class="slds-p-around_medium" label="Name" name="accName" onchange={handleNameChange}
required></lightning-input>
<lightning-input class="slds-p-around_medium" label="Phone" type="phone" name="accPhone"
onchange={handlePhoneChange} required></lightning-input>
<br />
<lightning-button class="slds-m-left_x-small" label="Save" variant="brand"
onclick={save}></lightning-button>
</div>
</div>
</lightning-card>
</template>
//js
import { LightningElement } from "lwc";
import { createRecord } from "lightning/uiRecordApi";
import { ShowToastEvent } from "lightning/platformShowToastEvent";
export default class ValidateRecordEditForm extends LightningElement {
accName;
accPhone;
handleNameChange(event) {
this.accName = event.target.value;
}
handlePhoneChange(event) {
this.accPhone = event.target.value;
}
save() {
const isInputsCorrect = [
...this.template.querySelectorAll("lightning-input")
].reduce((validSoFar, inputField) => {
inputField.reportValidity();
return validSoFar && inputField.checkValidity();
}, true);
if (isInputsCorrect) {
let fields = {
Name: this.accName,
Phone: this.accPhone
};
let objRecordInput = { apiName: "Account", fields };
createRecord(objRecordInput)
.then(() => {
this.dispatchEvent(
new ShowToastEvent({
title: "Success",
message: "Account Created Successfully",
variant: "success"
})
);
})
.catch((error) => {
this.dispatchEvent(
new ShowToastEvent({
title: "Error",
message: error.message,
variant: "error"
})
);
});
}
}
}
10.empAPI는 subscription의 한도가 있는 것 같은데
멀쩡히 동작하던 코드가 작동하지 않게 되었다.
강의에서 보여주는 코드는 작동이 제대로 되지 않아
사족 주석들과 불필요한 부분들을 개선하며 주기적으로 동작 확인을 했는데
구독해제를 하지 않고 구독만 보내다 보니 문제가 발생했을 것 같다.
어쨌든 코드적으로는 이해를 하고 수정이 가능한 상태였고
구독 제한이 7이라면 실제로 쓰기에는 조금 애매한 기능이기 떄문에
이해하고 넘어간다는 점에서 만족해야겠다.
//html
<template>
<lightning-card title="EmpApi Example" icon-name="custom:custom14">
<div class="slds-m-around_medium">
<p>
Use the buttons below to subscribe and unsubscribe to a streaming
channel!
</p>
<lightning-input label="Channel Name" value={channelName} onchange={handleChannelName}></lightning-input>
<br />
<lightning-button variant="success" label="Subscribe" title="Subscribe" onclick={handleSubscribe}
disabled={isSubscribeDisabled} class="slds-m-left_x-small"></lightning-button>
<lightning-button variant="destructive" label="Unsubscribe" title="Unsubscribe" onclick={handleUnsubscribe}
disabled={isUnsubscribeDisabled} class="slds-m-left_x-small"></lightning-button>
<br />
<br />
<p>Message : {message}</p>
</div>
</lightning-card>
</template>
//js
import { LightningElement, track } from "lwc";
import { subscribe, unsubscribe, onError } from "lightning/empApi";
export default class EmpAPIListner extends LightningElement {
@track message = "";
channelName = "/event/CustomEvent__e";
isSubscribeDisabled = false;
isUnsubscribeDisabled = true;
subscription = {};
handleChannelName(event) {
this.channelName = event.target.value;
}
connectedCallback() {
this.registerErrorListener();
}
handleSubscribe() {
subscribe(this.channelName).then((response) => {
this.subscription = response;
this.message = response.data.payload.message__c;
this.toggleSubscribeButton(true);
});
}
handleUnsubscribe() {
unsubscribe(this.subscription, () => {
this.toggleSubscribeButton(false);
});
}
toggleSubscribeButton(enableSubscribe) {
this.isSubscribeDisabled = enableSubscribe;
this.isUnsubscribeDisabled = !enableSubscribe;
}
registerErrorListener() {
onError((error) => {
console.log("Received error from server: ", JSON.stringify(error));
});
}
}
//send message with event
CustomEvent__e event = new CustomEvent__e(message__c = 'You are Awesome...');
Database.SaveResult result = EventBus.publish(event);
11.이번에는 드디어 유효성검사에 들어갔는데 console을 찍어보니 아래와 같은 모습을 볼 수 있었다.
결과적으로 보면 x에 문자열을 할당하고
replace /\+/g를 통해 숫자가 아닌 문자열을 모두 제거한 다음
3개, 3개, 4개로 각각의 자리를 채워주는 형태였는데
해당 값을 다시 조회해 2번째 인덱스에 (index 0에는 원본 값이 들어간다)
값이 있는 경우 앞의 세 글자에는 소괄호를 씌워주고 공백과 2번쨰 값을 넣어주며
다시 세번쨰 값이 존재하는지 확인 후 존재한다면 ‘-’를 추가해 앞의 숫자와 구분해줬다.
마지막으로 초기 match에서는 3, 3, 4개의 숫자만 들어갔기 때문에
초과된 숫자들은 최종 값 변환에서 사용되지 않아 더 이상의 숫자 입력이 제한된다.
추가적으로 html에서 패턴과 다를 경우 출력할 값들도 지정해줄 수 있었다.
//html
<template>
<lightning-card title="Input Mask in Lightning Web Component" icon-name="utility:phone">
<div style="padding: 10px">
<lightning-input name="PhoneNumber" label="Phone Number" value={phoneNumber}
pattern="^\(\d{3}\)\s\d{3}-\d{4}$" message-when-pattern-mismatch="Phone number is not valid"
message-when-value-missing="Phone number is required" onchange={handleInputMask} required>
</lightning-input>
</div>
</lightning-card>
</template>
//js
import { LightningElement } from "lwc";
export default class InputMask extends LightningElement {
phoneNumber;
handleInputMask(event) {
const x = event.target.value
.replace(/\D+/g, "")
.match(/(\d{0,3})(\d{0,3})(\d{0,4})/);
console.log(x, x[1], x[2], x[3]);
event.target.value = !x[2]
? x[1]
: `(${x[1]}) ${x[2]}` + (x[3] ? `-${x[3]}` : ``);
}
}
12.이번에는 값을 가져와서 뿌려줬는데 지금 다시 확인하니 index는 필요 없는 값인 것 같다.
class에서는 어떻게 필요한 데이터 형태를 잡아서 생성 후 쿼리로 받은 값을 보낼 수 있는지 알 수 있었고
html 부분 또한 쓸모없는 input value는 들어가 있지만
테이블에 참고할만한 데이터로 볼 수 있을 것 같다.
js부분도 엉망이었는데
체크를 해도 체크박스에는 변화가 없고
get 버튼을 누를 때 마다 배열에 값이 추가만 되기 때문에 누를 때 마다 값이 증가해버리는 현상이 발생했다.
자체적으로 boxList를 생성해 체크박스의 버튼 체크 상태도 관리해줬고
(기존에는 간직한 배열 내부의 정보만 수정해서 보이지는 않았다)
불필요한 주석과 코드를 제거하고 var를 수정하니 적당히 참고할만한 코드가 된 것 같다.
//apex class
public with sharing class wrapperTable {
public wrapperTable(){}
@AuraEnabled(cacheable=true)
public static String getAccounts(){
Integer rowIndex = 0;
List<accountWrap> accWrapList = new List<accountWrap>();
try {
List<Account> accList = [SELECT Id, Name, Phone FROM Account limit 10];
for(Account a : accList){
accWrapList.add(new accountWrap(a.Id,a.Name,a.Phone,rowIndex));
rowIndex++;
}
return JSON.serialize(accWrapList);
} catch (Exception e) {
throw new AuraHandledException(e.getMessage());
}
}
//class로 type 잡아주는 모습
public class accountWrap{
public String Id;
public String AccountName;
public String Phone;
public Boolean isSelected;
public Integer index;
public accountWrap(String Id, String AccountName, String Phone, Integer index){
this.Id = Id;
this.AccountName = AccountName;
this.Phone = Phone;
this.isSelected = false;
this.index = index;
}
}
}
//html
<template>
<div style="padding: 10px">
<div style="padding: 10px">
<table class="
slds-table
slds-table_cell-buffer
slds-table_bordered
slds-table_col-bordered
" border="1" width="100%">
<thead>
<tr class="slds-line-height_reset">
<td>
<lightning-input type="checkbox" onchange={handleAllChange}></lightning-input>
</td>
<td style="font-weight: bold">Account Name</td>
<td style="font-weight: bold">Phone</td>
</tr>
</thead>
<template for:each={accList} for:item="acc">
<tr key={acc.Id} class="slds-hint-parent">
<td>
<lightning-input type="checkbox" checked={acc.isSelected} onchange={handleCheckChange}
value={acc.index}></lightning-input>
</td>
<td>{acc.AccountName}</td>
<td>{acc.Phone}</td>
</tr>
</template>
</table>
</div>
<br />
<lightning-button variant="brand" label="Get Selected Accounts"
onclick={getSelectedAccounts}></lightning-button>
</div>
</template>
//js
import { wire, LightningElement } from "lwc";
import getAccList from "@salesforce/apex/wrapperTable.getAccounts";
export default class WrapperTable extends LightningElement {
selectedAccounts = [];
accList;
@wire(getAccList)
wiredRecord({ error, data }) {
if (error) {
let message = "Unknown error";
if (Array.isArray(error.body)) {
message = error.body.map((e) => e.message).join(", ");
} else if (typeof error.body.message === "string") {
message = error.body.message;
console.log(message);
}
} else if (data) {
this.accList = JSON.parse(data);
}
}
handleAllChange(event) {
const boxList = this.template.querySelectorAll("lightning-input");
for (let i = 0; i < this.accList.length; i++) {
this.accList[i].isSelected = event.target.checked;
boxList[i + 1].checked = event.target.checked;
}
}
handleCheckChange(event) {
this.accList[event.target.value].isSelected = event.target.checked;
}
getSelectedAccounts() {
for (let i = 0; i < this.accList.length; i++) {
if (this.accList[i].isSelected) {
this.selectedAccounts.push(this.accList[i]);
}
}
//출력 내용 확인용
console.log("Accounts Number : " + this.selectedAccounts.length);
console.log("details : " + JSON.stringify(this.selectedAccounts));
//처리가 끝난 후 초기화 해줘야 쌓이지 않는다
this.selectedAccounts = new Array(0);
}
}
13.chart.js는 되지 않는 것 같다.
애초에 된다고 우기는 코드에는 track도 들어있는데 사용하지 않고 있고
import로 추가적인 데이터를 받아오는 것도 아니고
이런저런 추가적 조치를 취했지만 되지 않는데
당장 과제가 급하기 때문에 코드만 저장해두고 나중에 돌아보던지 해야 할 것 같다.
추가로 static에 chart 파일까지 넣었는데 이 부분도 다음에 할 때 잊지 말아야겠다.
//html
<template>
<div class="slds-grid slds-grid--align-center" style="background: white">
<canvas width="400" height="400"></canvas>
</div>
</template>
//js
import { LightningElement } from "lwc";
import chartjs from "@salesforce/resourceUrl/ChartJs";
import { loadScript } from "lightning/platformResourceLoader";
import { ShowToastEvent } from "lightning/platformShowToastEvent";
export default class PolarAreaChart extends LightningElement {
chart;
config = {
type: "polarArea",
data: {
labels: ["Red", "Green", "Yellow", "Grey", "Blue"],
datasets: [
{
label: "My First Dataset",
data: [11, 16, 7, 3, 14],
backgroundColor: [
"rgb(255, 99, 132)",
"rgb(75, 192, 192)",
"rgb(255, 205, 86)",
"rgb(201, 203, 207)",
"rgb(54, 162, 235)"
]
}
]
}
};
renderedCallback() {
Promise.all([loadScript(this, chartjs)])
.then(() => {
window.Chart.platform.disableCSSInjection = true;
const canvas = document.createElement("canvas");
this.template.querySelector("div.chart").appendChild(canvas);
const ctx = this.template.querySelector("canvas");
this.chart = new window.Chart(ctx, this.config);
})
.catch((error) => {
this.dispatchEvent(
new ShowToastEvent({
title: "Error",
message: error.message,
variant: "error"
})
);
});
}
}
14.이제는 대부분의 코드를 슬쩍 보여주고 넘어가는 방식으로 강의가 진행되어서
class를 새로 만들거나 하는 모습이 없이 import 부분을 보고 직접 생성했는데
class를 만들지 않고 기존의 class에 넣고 해서 class가 조금 지저분하다.
사실 수정 부분은 기본적인 crud 느낌이기 때문에 별다를 것이 없지만
css적용이나 update를 하는방식에 대해 조금 참고할 정도는 되는 것 같다.
//apex class(하단의 getSingleContact만 사용된다
public with sharing class AccountController {
@AuraEnabled(cacheable=true)
public static List<Account> getAccList() {
return [SELECT Id, Name FROM ACCOUNT ORDER BY CreatedDate DESC LIMIT 10];
}
@AuraEnabled(cacheable=true)
public static List<Account> findAccList(String keyword) {
String key = '%' + keyword + '%';
return [
SELECT Id, Name, Phone
FROM ACCOUNT
WHERE Name LIKE :key
ORDER BY CreatedDate DESC
];
}
@AuraEnabled(cacheable=true)
public static Account getSingleAccount() {
return [
SELECT Id, Name, Phone
FROM ACCOUNT
ORDER BY CreatedDate DESC
LIMIT 1
];
}
@AuraEnabled(cacheable=true)
public static Contact getSingleContact() {
return [
SELECT Id, FirstName
FROM CONTACT
ORDER BY CreatedDate DESC
LIMIT 1
];
}
}
//html
<template>
<lightning-card title="Custom Inline Edit" icon-name="utility:edit">
<div style="padding: 10px; font-weight: bold; width: 250px">
<div class="slds-grid slds-gutters">
<div class="slds-col">
<span>Hello I am</span>
</div>
<div class="slds-col">
<template if:false={editFirstName}>
<span style="border-bottom: 1px dotted black">{firstName}
<lightning-button-icon class="slds-float_right" icon-name="utility:edit"
alternative-text="Update First Name" title="Update First Name" variant="bare"
size="medium" onclick={handleFirstNameEdit}></lightning-button-icon>
</span>
</template>
<template if:true={editFirstName}>
<lightning-input name="fileExpirationDate" value={firstName} label=""
onchange={handleFirstNameChange}></lightning-input>
<lightning-button-icon class="slds-float_right" icon-name="utility:save"
alternative-text="Update First Name" title="Update First Name" variant="bare" size="large"
onclick={handleUpdateFirstName}></lightning-button-icon>
</template>
</div>
</div>
</div>
</lightning-card>
</template>
//js
import { LightningElement, track } from "lwc";
import getSingleContact from "@salesforce/apex/AccountController.getSingleContact";
import { updateRecord } from "lightning/uiRecordApi";
import ID_FIELD from "@salesforce/schema/Contact.Id";
import FIRSTNAME_FIELD from "@salesforce/schema/Contact.FirstName";
export default class EditName extends LightningElement {
@track contact;
@track contactId;
@track firstName;
@track error;
@track editFirstName = false;
connectedCallback() {
getSingleContact()
.then((result) => {
this.contactId = result.Id;
this.firstName = result.FirstName;
})
.catch((error) => {
this.error = error;
});
}
handleFirstNameEdit() {
this.editFirstName = true;
}
handleFirstNameChange(event) {
this.firstName = event.target.value;
}
handleUpdateFirstName() {
const fields = {};
fields[ID_FIELD.fieldApiName] = this.contactId;
fields[FIRSTNAME_FIELD.fieldApiName] = this.firstName;
const recordInput = { fields };
updateRecord(recordInput)
.then(() => {
this.editFirstName = false;
})
.catch((error) => {
console.log("Error updating date => " + error.body.message);
});
}
}
(1).백준 15780번 멀티탭 충분하니?는 연속해서 멀티탭을 사용하지 않는 조건이었는데
간단하게 보자면 홀수일 때는 (n+1)/2, 짝수일 때는 n/2개의 공간을 사용할 수 있다는 말이었다.
Math.round로 간단하게 해결할 수 있었다.
const input = `6 2
3 4`.split('\n').map(el => el.split(' ').map(Number))
let sum = 0
for(let i = 0 ; i < input[1].length ; i++){
sum += Math.round(input[1][i]/2)
}
console.log(sum >= input[0][0] ? 'YES' : 'NO')