diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..c18dd8d
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1 @@
+__pycache__/
diff --git a/src/.streamlit/config.toml b/src/.streamlit/config.toml
new file mode 100644
index 0000000..88fd354
--- /dev/null
+++ b/src/.streamlit/config.toml
@@ -0,0 +1,5 @@
+[theme]
+primaryColor="#C00000"
+backgroundColor="#FFFFFF"
+secondaryBackgroundColor="#F0F2F6"
+textColor="#31333F"
diff --git a/src/PATHS.py b/src/PATHS.py
new file mode 100644
index 0000000..af8883c
--- /dev/null
+++ b/src/PATHS.py
@@ -0,0 +1,17 @@
+NAVBAR_PATHS = {
+ 'HOME':'home',
+ 'WISHLIST': 'wishlist',
+ 'FRIENDLIST': 'friendlist'
+}
+
+FOOTER_PATHS = {
+ # 'GitHub Repository':'https://github.com/landog893/Gifter-2',
+ 'Code of Conduct': 'https://github.com/landog893/Gifter-2/blob/main/CODE_OF_CONDUCT.md',
+ 'MIT License': 'https://github.com/landog893/Gifter-2/blob/main/LICENSE',
+ "Made with Streamlit": 'https://streamlit.io/'
+}
+
+SETTINGS = {
+ 'PROFILE':'profile',
+ 'LOGOUT':'logout'
+}
\ No newline at end of file
diff --git a/src/__pycache__/PATHS.cpython-39.pyc b/src/__pycache__/PATHS.cpython-39.pyc
new file mode 100644
index 0000000..32536c6
Binary files /dev/null and b/src/__pycache__/PATHS.cpython-39.pyc differ
diff --git a/src/__pycache__/account.cpython-39.pyc b/src/__pycache__/account.cpython-39.pyc
index febb34f..f736fe7 100644
Binary files a/src/__pycache__/account.cpython-39.pyc and b/src/__pycache__/account.cpython-39.pyc differ
diff --git a/src/__pycache__/account_info.cpython-39.pyc b/src/__pycache__/account_info.cpython-39.pyc
index 2e6ea12..90bca32 100644
Binary files a/src/__pycache__/account_info.cpython-39.pyc and b/src/__pycache__/account_info.cpython-39.pyc differ
diff --git a/src/__pycache__/item.cpython-39.pyc b/src/__pycache__/item.cpython-39.pyc
index a08be78..7dcb6d9 100644
Binary files a/src/__pycache__/item.cpython-39.pyc and b/src/__pycache__/item.cpython-39.pyc differ
diff --git a/src/__pycache__/item_manager.cpython-39.pyc b/src/__pycache__/item_manager.cpython-39.pyc
index 1acaefa..54561f6 100644
Binary files a/src/__pycache__/item_manager.cpython-39.pyc and b/src/__pycache__/item_manager.cpython-39.pyc differ
diff --git a/src/__pycache__/utils.cpython-39.pyc b/src/__pycache__/utils.cpython-39.pyc
new file mode 100644
index 0000000..c870cfd
Binary files /dev/null and b/src/__pycache__/utils.cpython-39.pyc differ
diff --git a/src/account_info.py b/src/account_info.py
index a781c3d..370d648 100644
--- a/src/account_info.py
+++ b/src/account_info.py
@@ -282,6 +282,33 @@ def get_friendlist(self, ID):
result_dict = result.values
return result_dict[0][6]
+ def find_id(self, ID) :
+ query = """Select "Name","Surname","Birthday","UserName","Password","Interests","WishList","FriendList"
+ From "Account" WHERE "ID" = %s;"""
+ conn = None
+ user_info = None
+ try:
+ # initializing connection
+ params = config()
+ print('Connecting to the PostgreSQL database...')
+ conn = psycopg2.connect(**params)
+ cur = conn.cursor()
+ # execute a statement
+ cur.execute(query, (ID,))
+ user_info = cur.fetchall()
+ cur.close()
+ conn.commit()
+ cur.close()
+
+ except (Exception, psycopg2.DatabaseError) as error:
+ print(error)
+ finally:
+ if conn is not None:
+ conn.close()
+ print('Database connection closed.')
+
+ return user_info[0].ID if user_info else 0
+
class Friends(AccountInfo):
def __init__(self):
diff --git a/src/assets/images/gift-flat.ico b/src/assets/images/gift-flat.ico
new file mode 100644
index 0000000..0eff60f
Binary files /dev/null and b/src/assets/images/gift-flat.ico differ
diff --git a/src/assets/images/gift-flat.png b/src/assets/images/gift-flat.png
new file mode 100644
index 0000000..f029f02
Binary files /dev/null and b/src/assets/images/gift-flat.png differ
diff --git a/src/assets/images/github-logo.png b/src/assets/images/github-logo.png
new file mode 100644
index 0000000..9490ffc
Binary files /dev/null and b/src/assets/images/github-logo.png differ
diff --git a/src/assets/images/profile.png b/src/assets/images/profile.png
new file mode 100644
index 0000000..c84a0c7
Binary files /dev/null and b/src/assets/images/profile.png differ
diff --git a/src/assets/images/settings.png b/src/assets/images/settings.png
new file mode 100644
index 0000000..783cd9f
Binary files /dev/null and b/src/assets/images/settings.png differ
diff --git a/src/assets/styles.css b/src/assets/styles.css
new file mode 100644
index 0000000..e4751ef
--- /dev/null
+++ b/src/assets/styles.css
@@ -0,0 +1,289 @@
+.navbar{
+ font-family: 'Trebuchet MS', sans-serif;
+ position: fixed;
+ width: 100%;
+ background: #C00000;
+ z-index: 99999999999999999999;
+ left: 0rem;
+ top: 0rem;
+ height: 55px;
+ padding-left: 4rem;
+}
+
+
+.navitem{
+ float: left;
+ display: block;
+ color: #f2f2f2 !important;
+ text-align: center;
+ padding: 17px 20px;
+ text-decoration: none !important;
+ border-bottom: 2px solid transparent;
+ font-size: 14px;
+ font-family: 'Trebuchet MS', sans-serif;
+}
+
+.navitem:hover{
+ background-color: rgba(221, 221, 221, 0.233);
+ height: 55px !important;
+
+}
+
+
+.settings{
+ height:1rem;
+}
+
+.version-span{
+ color: white;
+ position: absolute;
+ right: 1rem;
+ bottom: 1.75rem;
+ opacity: 0.5;
+ z-index: 9999;
+ border: 2px solid #333;
+ border-radius: 25px;
+ padding-right: 0.5rem;
+ padding-left: 0.5rem;
+ background: #333;
+}
+
+.dropbtn {
+ background-color: transparent;
+ color: white;
+ padding: 10px;
+ font-size: 16px;
+ height: 55px;
+ opacity: 0.5;
+ filter: invert(1);
+ cursor: pointer;
+ transition: 0.3s;
+ display: inline-block;
+ margin: 0;
+}
+
+.dropbtn:hover, .dropbtn:focus, .dropdown:hover .dropbtn {
+ opacity:1;
+}
+
+.dropdown {
+ position: fixed;
+ display: block;
+ right: 0rem;
+ top: 0rem;
+ padding-left: 0.7rem;
+ user-select: none;
+ padding-right: 10px;
+}
+
+.dropdown-content {
+ visibility: hidden;
+ position: fixed;
+ border-radius: 9px;
+ background-color: #C00000;
+ min-width: 160px;
+ overflow: auto;
+ right:0.3rem;
+ box-shadow: 0px 8px 16px 0px rgba(0,0,0,0.2);
+ z-index: 1;
+}
+
+.dropdown-content a {
+ color: white;
+ padding: 12px 16px;
+ text-decoration: none;
+ display: block;
+}
+
+.dropdown:hover{
+ background-color: rgba(221, 221, 221, 0.233);
+ background: rgba(221, 221, 221, 0.233);
+}
+
+.dropdown a:hover {background-color: rgba(221, 221, 221, 0.233);}
+
+iframe{
+ height: 0rem;
+}
+
+.stTabs {
+ display: none;
+}
+
+.navName {
+ display: inline-block;
+ color: #f2f2f2 !important;
+ text-align: center;
+ text-decoration: none !important;
+ border-bottom: 2px solid transparent;
+ font-size: 14px;
+ font-family: 'Trebuchet MS', sans-serif;
+ position: static;
+ right: 4rem;
+ margin: 0;
+
+}
+
+.hide {
+ display: none !important;
+}
+
+.footer{
+ font-family: 'Trebuchet MS', sans-serif;
+ position: relative;
+ width: 100%;
+ background: #ededed;
+ z-index: 99999999999999999999;
+ left: 0rem;
+ bottom: 0rem;
+ height: 70px;
+ /* padding-left: 4rem; */
+
+}
+
+.footerlist {
+ position: absolute;
+ width: 100%;
+ text-align: center;
+ background-color: #ededed;
+}
+
+.footeritem{
+ /* float: left; */
+ /* display: block; */
+ display: inline-block;
+ color: #000 !important;
+ text-align: center;
+ margin: 17px 20px;
+ text-decoration: none !important;
+ border-bottom: 2px solid transparent;
+ font-size: 14px;
+ font-family: 'Trebuchet MS', sans-serif;
+ filter: invert(.2);
+}
+
+.footeritem:hover{
+ filter: invert(.4);
+}
+
+footer:last-child {
+ display: none;
+}
+
+.gitHub {
+ display: inline-block;
+ width: 30px;
+ margin-top: -4px;
+ filter: invert(.2);
+
+}
+
+.block-container {
+ padding-top: 0 !important;
+ padding-bottom: 0 !important;
+}
+
+[data-testid="stHeader"] {
+ display: none;
+}
+
+[data-testid="stVerticalBlock"] > :nth-child(7) iframe {
+ height: unset;
+}
+
+blockquote {
+ color: rgb(49, 51, 63);
+ font-family: "Source Sans Pro", sans-serif;
+ font-weight: bold;
+ font-size: 1.5rem;
+ margin: 0;
+ border: none;
+}
+
+blockquote footer {
+ display: block !important;
+ text-align: right;
+ margin-top: 1rem;
+}
+
+blockquote footer:before {
+ content: "-";
+}
+
+.buttonDiv {
+ margin: 20px 0;
+}
+
+.block-container > div > [data-testid="stVerticalBlock"] {
+ display: block;
+}
+
+.block-container {
+ margin-top: 55px;
+}
+
+.show {
+ display: block !important;
+ background: pink !important;
+}
+
+button[kind="primary"], button[kind="formSubmit"]{
+ background-color: #C00000;
+ color: white;
+}
+
+button[kind="primary"]:hover, button[kind="formSubmit"]:hover{
+ background-color: #c00000a3;
+ color: white;
+}
+
+button[kind="primary"]:active, button[kind="formSubmit"]:active {
+ background-color: #c00000a3;
+ color: white;
+}
+
+.horizontalDiv {
+ width: unset !important;
+ display: inline-block;
+}
+
+.initial h2 {
+ text-align: center;
+ margin-bottom: 50px;
+ font-size: 3rem;
+}
+
+.initial .buttonDiv {
+ text-align: center;
+}
+
+.createaccount [data-testid="stVerticalBlock"] {
+ display: block;
+}
+
+.createaccount [data-testid="stVerticalBlock"] > div {
+ margin: 10px 0;
+}
+
+.createaccount [data-testid="stVerticalBlock"] > .horizontalDiv:last-child {
+ float: right;
+ margin-right: 10px;
+}
+
+.account .stMarkdown [data-testid="stMarkdownContainer"] > p {
+ margin-bottom: 3rem;
+ font-size: 20px;
+ margin-top: 1rem;
+}
+
+.wishlist [data-testid="stVerticalBlock"] {
+ display: block;
+}
+
+.wishlist .horizontalDiv {
+ margin-right: 25px;
+}
+
+.friendlist .horizontalDiv {
+ margin-right: 25px;
+}
\ No newline at end of file
diff --git a/src/main.py b/src/main.py
index b223f57..ff15e5c 100644
--- a/src/main.py
+++ b/src/main.py
@@ -7,26 +7,44 @@
from account_info import AccountInfo
from item import item
from datetime import datetime
+import streamlit.components.v1 as components
+import utils as utl
+import requests
import re
-
+from streamlit.components.v1 import html
+
+extrajs = ''''''
+
+def horizontalButtons():
+ global extrajs
+ extrajs += '''
+ forms = window.parent.document.querySelectorAll('[data-testid="stFormSubmitButton"]');
+ for (const element of forms) {
+ element.classList.add("horizontalDiv");
+ element.parentElement.classList.add("horizontalDiv");
+ element.parentElement.parentElement.classList.add("horizontalDiv");
+ }
+ '''
email_sent = False
def initial_page():
st.header("Gift Finder!")
- create = st.button('Create Account')
- login = st.button('Log In')
+ create = st.button('Create Account', type="primary")
+ login = st.button('Log In', type="primary")
if create:
st.session_state.runpage = 'createaccount'
st.experimental_rerun()
if login:
st.session_state.runpage = 'login'
st.experimental_rerun()
+
def login_page():
+ st.header("Login")
form1 = st.form(key='Login form')
f_name = form1.text_input('User Name')
password = form1.text_input('Enter a password', type='password')
- but = form1.form_submit_button('Log in')
+ but = form1.form_submit_button('Log in', type="primary")
if but:
accountMan = AccountInfo()
info = accountMan.get_account(f_name,password)
@@ -47,7 +65,7 @@ def login_page():
st.experimental_rerun()
def create_account():
- st.write('Please fill out the form')
+ # st.write('Please fill out the form')
form = st.form(key='Create_form')
f_name = form.text_input('First Name:')
surname = form.text_input('Last Name:')
@@ -57,8 +75,9 @@ def create_account():
username = form.text_input('User Name:')
password = form.text_input('Enter a password:', type='password')
interest = form.text_input('Interests (please enter them comma seperated):')
- but1 = form.form_submit_button('Submit')
-
+
+ but1 = form.form_submit_button('Submit', type="primary")
+ but2 = form.form_submit_button("Back")
# check if birthday is valid format
format = "%m/%d/%Y"
validB = True
@@ -109,26 +128,22 @@ def create_account():
st.experimental_rerun()
#return account
- if st.button('Back'):
+ if but2:
st.session_state.runpage = 'initial'
- st.experimental_rerun()
+ st.experimental_rerun()
+ horizontalButtons()
+
def account_page():
acc = st.session_state.account
st.header('Welcome ' + acc.name + '!')
- st.write("What a beautiful day to gift!")
- if st.button('Profile'):
- st.session_state.runpage = 'profile'
- st.experimental_rerun()
- if st.button('Wishlist'):
- st.session_state.runpage = 'wishlist'
- st.experimental_rerun()
- if st.button('Friendlist'):
- st.session_state.runpage = 'friendlist'
- st.experimental_rerun()
- if st.button('Logout'):
- st.session_state.runpage = 'initial'
- st.experimental_rerun()
+
+ st.write("Quote of the day:")
+ if 'response' not in st.session_state:
+ st.session_state.response = requests.get('https://zenquotes.io/api/today')
+ st.markdown(st.session_state.response.json()[0]["h"].replace('—', ''), unsafe_allow_html=True)
+
+
# check if whether a notification email needs to be sent today (is it 1 week before the account's birthday)
global email_sent
@@ -171,7 +186,7 @@ def profile_page():
st.write('Email: ' + acc.email)
st.write('Email Notifications: ' + acc.notifications)
st.write('Interests: ' + (acc.interests).replace("\"", ""))
- if st.button("Edit Profile"):
+ if st.button("Edit Profile", type="primary"):
st.session_state.runpage = 'editprofile'
st.experimental_rerun()
if st.button("Back"):
@@ -200,7 +215,7 @@ def editprofile_page():
case = -1
chars = set("~!@#$%^&*()_+=")
- if form.form_submit_button('Update'):
+ if form.form_submit_button('Update', type="primary"):
# check if birthday is valid format
format = "%m/%d/%Y"
validB = True
@@ -293,18 +308,32 @@ def wishlist_page():
j = j + 1
# st.table(df)
- if st.button('Add item'):
+ if st.button('Add item', type="primary"):
st.session_state.runpage = 'additem'
st.experimental_rerun()
- # if st.button('Modify item'):
- # st.session_state.runpage = 'modifyitem'
- # st.experimental_rerun()
- # if st.button('Remove item'):
- # st.session_state.runpage = 'deleteitem'
- # st.experimental_rerun()
+ if st.button('Modify item', type="primary"):
+ st.session_state.runpage = 'modifyitem'
+ st.experimental_rerun()
+ if st.button('Remove item', type="primary"):
+ st.session_state.runpage = 'deleteitem'
+ st.experimental_rerun()
+
if st.button('Back'):
st.session_state.runpage = 'account'
- st.experimental_rerun()
+ st.experimental_rerun()
+ global extrajs
+ extrajs += '''
+ document.addEventListener('DOMContentLoaded', function(event) {
+ //the event occurred
+ forms = window.parent.document.querySelectorAll('.stButton');
+ for (const element of forms) {
+ element.classList.add("horizontalDiv");
+ element.parentElement.classList.add("horizontalDiv");
+
+ }
+ forms[forms.length - 1].parentElement.classList.add("back");
+ })
+ '''
def additem_page():
form = st.form(key='AddItemForm')
@@ -315,7 +344,7 @@ def additem_page():
case = -1
chars = set("~!@#$%^&*()_+=")
- if form.form_submit_button('Add item'):
+ if form.form_submit_button('Add item', type="primary"):
if title == "":
case = 0
else:
@@ -375,16 +404,17 @@ def modifyitem_page():
# except ValueError:
# case = 1
- # if case == 0: st.error("Item ID does not exist")
- # elif case == 1: st.error("Item ID must be an integer")
- # else:
+
+ if case == 0: st.error("Item ID does not exist")
+ elif case == 1: st.error("Item ID must be an integer")
+ else:
form = st.form(key='ModifyItemForm')
title = form.text_input('Title:', value= i.title, placeholder= i.title)
desc = form.text_input('Description', value= i.desc, placeholder= i.desc)
link = form.text_input('Link', value= i.link, placeholder= i.link)
cost = form.text_input('Cost', value= i.cost, placeholder= i.cost)
chars = set("~!@#$%^&*()_+=")
- if form.form_submit_button('Modify item'):
+ if form.form_submit_button('Modify item', type="primary"):
if title == "":
st.error("Title is not nullable")
elif any((c in chars) for c in title):
@@ -398,7 +428,7 @@ def modifyitem_page():
st.experimental_rerun()
except ValueError: st.error("Cost must be a number")
- if st.button('Back'):
+ if st.button('Back', type="primary"):
st.session_state.runpage = 'wishlist'
st.experimental_rerun()
@@ -412,7 +442,7 @@ def deleteitem_page():
# id =form.text_input('Please enter ID of the item you want to delete', value=items[0])
# case = -1
- # if form.form_submit_button('Delete item'):
+ # if form.form_submit_button('Delete item', type="primary"):
# try:
# i = item(ID=int(id))
# except ValueError:
@@ -461,6 +491,7 @@ def friendlist_page():
if friendlist != 'NaN':
friendlist = friendlist.split(',')
friendobj = [Account(ID=int(f)) for f in friendlist if f.isnumeric()]
+
# friendName = [f.name for f in friendobj]
# friendSur = [f.surname for f in friendobj]
# df = pd.DataFrame(list(zip(friendlist,friendName,friendSur)), columns=('ID', 'First Name', 'Last Name'))
@@ -493,18 +524,31 @@ def friendlist_page():
j = j + 1
- # if st.button('View Wishlist of friend'):
+ # if st.button('View Wishlist of friend', type="primary"):
# st.session_state.runpage = 'friendwishlist'
# st.experimental_rerun()
- if st.button('Add friend'):
+ if st.button('Add friend', type="primary"):
st.session_state.runpage = 'addfriend'
st.experimental_rerun()
- # if st.button('Delete friend'):
+ # if st.button('Delete friend', type="primary"):
# st.session_state.runpage = 'deletefriend'
# st.experimental_rerun()
- if st.button('Back'):
+ if st.button('Back', type="primary"):
st.session_state.runpage = 'account'
st.experimental_rerun()
+ global extrajs
+ extrajs += '''
+ document.addEventListener('DOMContentLoaded', function(event) {
+ //the event occurred
+ forms = window.parent.document.querySelectorAll('.stButton');
+ for (const element of forms) {
+ element.classList.add("horizontalDiv");
+ element.parentElement.classList.add("horizontalDiv");
+
+ }
+ forms[forms.length - 1].parentElement.classList.add("back");
+ })
+ '''
def viewwishlist_page():
acc = st.session_state.account
@@ -520,6 +564,7 @@ def viewwishlist_page():
item_objs = [item(ID=id) for id in items]
except ValueError:
st.error("This ID doesn't have any wishlist")
+
item_titles = [(i.title).replace("\"", "") for i in item_objs]
@@ -539,21 +584,15 @@ def addfriend_page():
acc = st.session_state.account
friendlist = acc.friendlist
form = st.form(key='addfriend')
- id =form.text_input('Please enter ID of the friend')
- case = -1
+ username =form.text_input('Please enter the username of the friend')
+ id = -1
- if form.form_submit_button('Add friend'):
- try:
- friend = Account(ID=int(id))
- except ValueError:
- case = 0
-
- try: int(id)
- except ValueError:
- case = 1
+ if form.form_submit_button('Add friend', type="primary"):
+
+ accountMan = AccountInfo()
+ id = accountMan.find_id(username)
- if case == 0: st.error("Friend ID does not exist")
- elif case == 1: st.error("Friend ID must be an integer")
+ if id == 0: st.error("Friend Username does not exist")
else:
if friendlist:
friendlist += ',' + str(id)
@@ -588,11 +627,12 @@ def deletefriend_page():
print(id)
# case = -1
- # if form.form_submit_button('Delete friend'):
+ # if form.form_submit_button('Delete friend', type="primary"):
# try:
# Account(ID=int(id))
# except ValueError:
# case = 0
+
# try: int(id)
# except ValueError:
@@ -623,12 +663,54 @@ def deletefriend_page():
st.session_state.runpage = 'friendlist'
st.experimental_rerun()
+if 'account' not in st.session_state or st.session_state.runpage == 'initial':
+ st.session_state.account = 'None'
-if 'runpage' not in st.session_state:
+if 'runpage' not in st.session_state or (st.session_state.account == 'None' and not st.session_state.runpage == 'login' and not st.session_state.runpage == 'createaccount'):
st.session_state.runpage = 'initial'
-if 'account' not in st.session_state:
- st.session_state.account = 'None'
+# st.set_page_config(layout="wide", page_title='Navbar sample')
+st.set_page_config(page_title='Gifter 2', page_icon='assets/images/gift-flat.ico')
+st.set_option('deprecation.showPyplotGlobalUse', False)
+utl.inject_custom_css()
+utl.navbar_component(st.session_state.account)
+
+
+navtab, tab2= st.tabs(["Navtab", "test"])
+
+with navtab:
+ if st.button('home'):
+ st.session_state.runpage = 'account'
+ st.experimental_rerun()
+ if st.button('wishlist'):
+ st.session_state.runpage = 'wishlist'
+ st.experimental_rerun()
+ if st.button('friendlist'):
+ st.session_state.runpage = 'friendlist'
+ st.experimental_rerun()
+ if st.button('acount'):
+ st.session_state.runpage = 'profile'
+ st.experimental_rerun()
+ if st.button('logout'):
+ st.session_state.runpage = 'initial'
+ del st.session_state["account"]
+ st.experimental_rerun()
+with tab2:
+ st.header("Placeholder")
+
+
+
+extrajs = '''
+ buttonDivs = window.parent.document.querySelectorAll('[data-testid="stVerticalBlock"] > [data-stale="false"] > .stButton');
+ while (!buttonDivs) {
+ buttonDivs = window.parent.document.querySelectorAll('[data-testid="stVerticalBlock"] > [data-stale="false"] > .stButton');
+ }
+ for (const element of buttonDivs) {
+ element.parentElement.classList.add("but");
+ }
+ '''
+
+# navigation()
if st.session_state.runpage == 'initial':
initial_page()
@@ -658,3 +740,19 @@ def deletefriend_page():
addfriend_page()
elif st.session_state.runpage == 'deletefriend':
deletefriend_page()
+
+extrajs += '''
+
+'''
+js = '''
+
+ '''
+ html(js)
+
+
+def footer_component():
+ with open("src/assets/images/github-logo.png", "rb") as image_file:
+ image_as_base64 = base64.b64encode(image_file.read())
+
+ footer_items = ''
+ for key, value in FOOTER_PATHS.items():
+ footer_items += (f'')
+#
+ component = rf'''
+
+ '''
+ st.markdown(component, unsafe_allow_html=True)
+ js = '''
+
+ '''
+ html(js)
\ No newline at end of file