## Terms & Techniques
* Docstring → Strings that act as supporting pieces of text or comments towards the code. They can be used to add information and are not executed as logic that appears as output in the terminal. Commonly used to help programmers understand the purpose of a specific piece of code. Denoted by having the text enclosed within triple quotation marks.
    * Structure: """text"""

* Data types → In Python, data types are categories of classifying data items. The specific data type assigned to a value determines the kind of data it represents, the ranges of values it can hold, and what operations can be performed on them.
    * Examples: string (str), integer (int), float, boolean (bool), dictionary (dict), list

* String (str) → A data type comprising a sequence of characters that is used to represent text. A string must be enclosed with either single or double quotation marks in Python. Strings can be displayed using the print() function.
    * Structure: "Hello"
    * Example: print('Hello')

* Integer (int) → A data type for WHOLE numbers.

* Float → A data type for DECIMALS.

* Variables → A container for storing data values. They are created when a variable is assigned or applied to it.
    * Structure: abc = 189
    * In the example above, if established that variable "abc" is 189, when using the string "abc" elsewhere in the code, it will run as "189".

* Functions → A reusable code that can be used to perform a specific task (print, input). Functions are defined by the "def" keyword. It only runs when it is called and also allows for the defining of parameters. To call a function, write its name as well as the associated parameters within a pair of brackets. It is good practice to use descriptive names that explain what the function does.
    * Structure: def function(parameter):
    * The parameters represent temporary variable names that act as placeholders for the data the function requires to perform its task. When calling the function, the actual values of these placeholders become the "arguments"

* Return keyword → It is used to exit a function and send a specific value back to the line of code that called it. The functioned ultimately "becomes" that value, allowing the program to store or use it for further calculations.

* Methods → A function that is available for a particular object because of its specific object type. For instance, python has built-in methods for:
    * Lists: .count(), .append(), .clear(), .reverse(), .pop()
    * Strings: .capitalize(), .count(), .join()

* F-string → Otherwise known as formatted strings, this allows one to format certain parts of a string and add placeholders for items such as variables.
    * Requires 2 things:
        1. Put an “f” just before the string outside of the quotations (e.g. f“Hello”)
        2. Add curly brackets for variables (e.g. {age})
    * Structure: f"str {variable}"
    * Example:

            price = 59
            txt = f"The price is {price:.2f} dollars"
            print(txt)

### Distinguishing between methods and functions:
>* Functions involve entering an object/parameter within the specified tool. In other words, apply len to the quote.
* Example: Where tool = len() and parameter = the variable "quote"
    * len(quote)

>* Methods involve performing a specific action directed towards an object. These are specifically attached towards to a specific data type. In other words, perform upper on yourself.
* Example: Where object = "quote" and action = .upper() (converts all lowercase strings into uppercase)
    * quote.upper()

## Functions
* Print → Printing a message as output on the screen/terminal.

* Input → A function that allows for taking input from the user. It causes the program to pause and wait for a user to type information in the terminal. To allow the program to continue, the user must then also press "Enter". By default, Python will take the data entered and return it to the program as a string. When using the input function, a prompt or parameter can be added to display a message that informs the user what they should be submitting to get the code to run.
    * NOTE: input() directly prints out the prompt without needing print()
    * Structure: input("parameter")
    * Example: input("Enter password: "). This informs the user to enter a password in the terminal

* Len → Counts and returns the number of individual characters within a sequence.
    * Structure: len(variable/"str")
    * Because it returns the number of individual characters, it can be the amount of words in a string or the number of items (aka indexes) in a list

## Methods
* .strip() → A method that removes spaces at the beginning and end of a str.
    * Structure: strip(characters)
    * Example: x = "  abc  "

        Doing *new = x.strip()* will remove spaces and cause *new = "abc"*

* .split() → Used to split a str given a specific parameter into individual items in a list.
    * Structure: split(parameter)
    * Example: x = "max and bob"

        Doing *new = x.split(" and ")* will remove spaces and cause *new = ["max", "bob"]*

* .capitalize() → Used to capitalise the first character.

* .upper() → Used to capitalise all characters in a str.

* .lower() → Used to convert all characters in a str into lowercase.

* .int(), .float(), .str() → CASTING
    * A method to convert python data types. It takes a int/float/str as an argument and returns int/float/str type object.

## "If" statements
> A control flow statement. It is used to control the flow of the program and execute a particular code if the condition entered is met/true. If the condition is false, it skips running the code.
>* NOTE: To establish the condition, it needs an indentation.
>* Structure: Always start with "if"

For 2 conditions, you can use "if" and "else". Else is the alternative code that will be executed in the event that the condition in the "if" statement is not met.
* Structure: user_name = input("Enter your name: ")

        if user_name == "xyz":
            print ("Welcome!")
        else:
            print("Access Denied.")

For more than 2 conditions: To establish another condition if the first "if" statement is not met, *else if* or "elif" statements can be used (in an unlimited amount). These are conditions that the program will keep running through until the first match is found. A final "else" can also be used but at the very end.
* Structure (continuing the above example):

        elif user_name == "abc":
            print ("Welcome!")

## Relation Symbols (to establish conditions)
|Operator|Name|
|--------|----|
|==|Equal|
|!=|Not equal to|
|>|Greater than|
|<|Less than|
|>=|Greater than or equal to|
|<=|Less than or equal to|

## Logical Operators
|Operator|Name|
|--------|----|
|and|Returns True if both statements are true|
|or|Returns True if one of the statements is true|
|not|Returns False if result is true|

## Loops
* While loops → A loop that runs through a block of code as long as a specified condition is true. When the condition is met or becomes false, the loop stops and the program directly runs the following line immediately after the loop.
    * Use when the number of iterations is UNKNOWN (e.g. asking for a number from the user)
    * Structure: input = int(input("Enter a number: "))
    * Example:

            while input > 0:
                print(f"T minus {input}")
                input -= 1 (This subtracts 1 after each iteration)
    * Example 2:

            while True:

            Using "while True" creates an infinite loop. TODO

* For loops → A loop that iterates over a sequence. It moves through the sequence each item at a time and executes the indented block of code for each one until the sequence ends.
    * Use when you have a predetermined number of iterations to make (KNOW how many times to repeat; fixed)
    * Structure for a list: For each_index in list:
    * Structure for a dictionary: For (key,value) in dict:

## Manual Counter (in a loop)
    count = 0 (first, initialise)
        *insert code to run*
        count += 1 (at the end, use this so that it increments a variable's value by 1)

## List
* Lists are used to store multiple items in a single variable.
* Defined by square brackets: []
* Structure: [index[0], index[1], index[2]]
* To add new items to a list: Use the append method:
    * Structure: list.append(item)

## Dictionary
* Dictionaries are a data-structure that allow you to store data in key-value pairs.
* Defined by curly brackets: {}
* Structure: {"key": "value"}
* Each key ONLY has 1 entry but different keys can have the same value (e.g. diff words having the same meaning)
* To print out a value of a key "name" in a dict: print(pet["name"])
* To access/parse through a nested dictionary (must use the method ".items()" which unpacks the entire dictionary including both keys and values):

        for key, value in dict.items():
            name = value["name"]

    * Returning only the keys in a dict: for key, value in dict.keys():
    * Returning only the values in a dict: for key, value in dict.values():
    * To remove a key-value pair: dict.pop("key")
    * To add items: dict["key"] = value

### Choosing between dictionary & list
* A list allows for duplicates (storing multiple instances of the same value)
* A list is better if you care about the order (alphabetical, etc) & need index-based access (e.g. obtaining the 3rd item)
* Use a list if iteration is the primary goal (looping through each item)

* A dictionary allows you to assign a unique key to specific data (values)
* Dictionaries allow for faster look-ups without having to search through the entire collection (because you know the specific key it is tied to)
* Ensures that each key is unique
* Use a dictionary if data is better described by name than it is by position


## Misc
* Rounding up to the nearest integer:
    * Use → round(*value*, 0)

* Initialising (When you have an empty variable, you can assign things as a means of initialising it):
    * None → As an optional resort to prevent "NoneType" errors. Will signal "Not Found" clearly
    * 0 → If summing or dealing with numbers
    * "" → If building a sentence with str

* Significant figures → p.p(f"Total: ${dep_tax:.2f}"),

* Excluding the last item from a list:
    * Use → list[:-1]
    * Here, the colon creates a new list that excludes the item in the last index

* Placement of colons in a list
    * [1:] → Start at index 1 and run everything until the last index
    * [:-1] → Run as normal but stopping point is the last item (exclude this)
    * [:2] → Stopping point is 2nd item (do not include index 3)

* For output in the terminal, if you want it to display on the next line (or create a line break), use:
    * \n → Add this to the end of a str just before the closing quotation mark
    * Example: f"Name: {pet['name']}\n"

* Assessing code:
    * Is it repetitive? How efficient?
    * Is the code functional and its purpose clear? How simple is it to understand?
    * Is it easy to change the conditions and edit it? Or is it rigid?
    * Is anything manual? Can it be streamlined using methods/functions?

* // → This is the floor division operator. It returns the whole number and discards the fractional part (decimal points)

* Python starts counting at 0 (known as zero-based indexing)

* If you want to keep asking the user to enter an input until they return an empty input by simply pressing "enter", use:

        while True:
            title = input("Enter a song title: ")
            if title == "":
                break

* Starting with FALSE:

        Example: Variable = False
        Most commonly used
        Assumes the condition has NOT been met and goes through the rest of the code below it (can be for loops, if statements etc)
        If it meets the correct/required condition, it will change the variable to true (e.g. Variable = True)
        POV: You need one success to win

* Starting with TRUE:

        Assumes the condition has been met (variable = True) and perfect until something comes along that causes it to no longer meet it anymore (usually if statements to establish points where it will fail)
        Example: Variable = True
        If condition == “”
        Variable = False
        POV: You need zero failures to win


## HTML
* There are 2 components:
    1. Web browsers → Chrome, Firefox, etc (on the computer that we interact with as a user). A software application used to access, navigate, and display information on the internet/World Wide Web. It retrieves data from web servers using HTTP and translates HTML code into visible web pages.
    2. Web server → Where the programming happens. A computer system or software that stores and delivers website content (e.g. HTML files, images, videos) to use on the internet.

* Tags → Keywords or letters enclosed in angle brackets that inform a web browser how to display or structure images, text, and links.
    * Structure: <.body>
    * There are 3 types:

            Opening tag → <xyz>
            Closing tag → </xyz>
            Comments tag → <!-- xyz –>

* Element → A building block of tags from start to end. Defined by a start tag, the content within it, and an end tag.

* Children → The element directly inside an element.
    * Example: In the code below, "li" is the child element of "ul"
    <.ul>
        <.li>
        <./li>
    <./ul>

* Attribute → Keywords placed within the opening tag of an element which provides information or controls the behaviour of that specific element. They are also typically structured as name-value pairs. For instance, the "img" attribute is used to embed an image in a HTML page.

* Misc items:
    * Use <.br> to split paragraphs or text (line break)
    * Use <.a> to attach a hyperlink
        * Structure: <.a href="" target="_blank">*text*<./a>. Target is used to open it in another page.

* Lists:
    * Ordered lists (numeric) → <.ol> <.li><./li> <./ol>
    * Unordered lists (dot points) → <.ul> <.li><./li> <./ul>

* Inserting images:
    * Structure: Within body, use <.img width="", height="" src="">

* Tables:
    * Use the parent tag <.table> <./table>
    * This works by writing things out row-by-row (tr). This means column data must be written within the row tags (td)
    * <.tr> <.td><./td> <./tr>

* Forms:
    * Used to collect user input (e.g. password, radio buttons, dropdown lists)
    * Name = The attribute that acts as a "key"
    * Value = The value that gets returned (output appearing as user input for radio buttons, dropdown lists etc)
    * Structure: <.form> <.input type="*insert*"> </.form>
    * To access/extract and use the user inputs from the form element, use:

            request.form.get("name")

    We use .get() because it acts as a safety net. If a field is left blank, the code has the potential to crash and produce a KeyError. The .get() method will return None if it is missing, allowing the code to still run.


### Linking a button (like submit) to another web page.

There are 2 methods:
1. Using “(action=/…)()”

    Here, you just add extra brackets on the p.form code

        p.form(action="/solution")(
            p.input(type="text"),
            p.input(type="submit", value="Calculate!")
            )

    The code reads:
    For the submit button which is displayed as “Calculate!”, when the user clicks on it, redirect them to the “/solution” web page.

2. Using formaction=“/…”

    Tells the computer where to go when the user clicks the button.

        p.form(
            p.input(type="text"),
            p.input(type="submit", value="Calculate!", formaction="/solution")
        )

    The code reads:
    For the button displayed as “Calculate!” with the type “submit”, when it is pressed on, go to the “/solution” web page.

    Note: Formaction is only used for buttons with the type=submit.
    Tip: Use this one if you have a lot of buttons in the same page that lead to diff web pages.

* Adding labels

        p.form(
            p.label("ID: ")(p.input(type="text")),
            ...
        )

### HTTP Methods
* CRUD operations → The 4 basic operations to manage data in a database. In HTTP, CRUD is applied through specific functions to inform a server what exactly to perform on a resource.

|Name|HTTP Function|Context|
|---|---|---|
|Create|POST|If you are sending a form (specifically sending data like username and password or processing calculations for a calculator to a new specified page)
|Read|GET|When a user types in a URL and the computer goes straight to the URL
|Update|PUT/PATCH|Changing something (when you give info to the server but it stays on the same webpage (potential for duplicates))
|Delete|DELETE|Deleting something

* Structure:

        @app.get("/*webpage name*")
        def function()
            return *html string*

    Alternatively, so you can directly use both GET and POST requests:

        @app.route("/*webpage name*", methods=["GET", "POST"])
        def function()
            return ...

* To redirect, it must be a post request that leads to another webpage and then a get request to the "get" version of that webpage

* Default example:

        @app.route("/name", methods=["GET", "POST"])(p.input(type="submit", value="Back")),
        def name():
            response = p.html(
                p.body(
                    p.form(

                    )
                    p.input(type="submit", value="ADD", formaction="/add_player")
                    ...
                )
            )

        return str(response)

* Drop-down list:

        p.form(
            p.label("Player 1:", for_="p1"),
            p.select(name="p1", id="p1")(
                [p.option(item["name"], value=item["name"])
                for item in data]),
        )

        Optional: Can add required=True as part of p.select(). Ensures the user picks an option from the list. If not the code won't run.

* Radio buttons:

        p.form(
            p.label("Faculty:", for_="faculty"),
                p.label("Business", for_="business"),
                p.input(type="radio", id="business", name="faculty", class_="faculty", value="business"),
                p.input(type="radio", id="engineering", name="faculty", class_="faculty", value="engineering"),
                p.label("Law", for_="law"),
        )

        NOTE: The "name" must be the same. This allows the user to only select 1 button out of all the available options.

* Text boxes:

        p.form(
                p.label("Name: ")(p.input(type="text")),
                ...
        )


## Cookies (Using *sessions* in flask)
* Storing user-specific data across requests using "cookies" via session (like login status)
    * Cookies are stored as a dictionary (key-value pairs). Basically used to store information. When the user inputs information into the browser, it acts as the middleman, allowing the server to receive (via a POST request) and store it in a dictionary by assigning a key which can then be accessed/used in the server. Then, the server sends back small pieces of data/information to the browser (with a unique key and value that has been encrypted using the secret key), which it then stores locally. This means whenever the browser sends a new request to the server, the cookie is always attached so it "remembers" the user requesting the action
    * You can clear/delete cookies on the browser but the server decides what goes in the cookie
    * Uses: Creating a history page, login page and details

    * NOTE: If using cookies to create a history page or other situation where you are updating data into the dictionary, you need to also use:

            request.session["key"].append("new_page")
            session.modified = True (THIS PART IS IMPORTANT!)

        The logic behind this is that the cookie has already been sent back to the browser once and stored, meaning the browser will keep "throwing" that old information to the server. Although, when making new changes (modifying the data inside the session dictionary on the server side), the server will not automatically send an updated cookie back to the browser for it to store. To make the cookie sync each time and ensure the server sends the response with the new and updated cookie, you need to specifically state that it has been modified so as to overwrite it.

* Requires using a secret key to encrypt it:

        app = Flask(__name__)
        app.secret_key = "nn2b0qcw89phv389ebrftsc4uqwe3q6"

* To delete/clear cookies:

        session["key"].clear()

## Serialisation
* Serialisation → Process of saving data in files.

### Cookies
* Pros of using cookies/session state for data storage/architecture:
    * Cookies are handled automatically by the browser and server once they have been established or set. This means the data travels back and forth and streamlines the overall process without having to manually write extra lines of code such as load() or write()
    * Allows for individualised storage since cookies naturally sort data into separate dictionaries for each user whereas JSON files often involve compiling data in a large and potentially crowded file (especially if the user base grows)

* Cons:
    * User can delete their data/cookies at any time because its physical storage is on the user's hard drive which the server has no control over. This means it cannot be relied on for long-term or permanent data storage
    * User can only view data associated with a specific browser or the person making the request. In other words, the server can only access data from 1 user's specific browser at a time since it is not possible to open each cookie all at once. Because of this, cookies cannot perform actions based on data obtained from all users like calculating averages (which requires a centralised database on the server)
    * Environment restrictions. Cookies are specific features of a web app, meaning they can only be saved within them and are only accessible during an active web-session. Thus, they cannot be used by non-web programs (unlike JSON files which are universal and exist as a "real" file)

### JSON files
* To save data without relying on the server. Keeps it in files.

* Components:
    * Reading → It is used to convert the JSON string/file into its equivalent python data structure. To parse/access the data stored within the JSON file (whether it be a list or dictionary etc)

            def read_json_file(filename):
                my_file = open(filename, "r")    (Opening the file)
                information = json.load(my_file) (Reading the file)
                my_file.close()                  (Close the file)
                return information

    * Writing → Saving/overwriting new information to the JSON file. In the first line for “(filename, info_to_save)”, this is the parameters you should enter when you want to use JSON to STORE data. Format: (store the name of the file, what information you want to save)

            def write_json_file(file_name, info_to_save)
                my_file = open(filename, "w")    (Opening the file)
                json.dump(info_to_save, my_file) (Writing the JSON-formatted-data into the file)
                my_file.close()                  (Closing the file)

* Structure:
    * Copy and paste the *jsonhelpers.py* file
    * In the server file use:

            from jsonhelpers import *

* If the JSON file itself to store the data does not exist yet (you want to create a new one/first time storing the data, this is the function you need to also use.
    * Format: (name of the file, { } for dictionary OR [ ] for list))

            def create_if_not_exist(filename, empty_data):
                if not os.path.exists(filename):
                    write_json_file(filename, empty_data)

* Uses: Storing passwords, any data that needs to exist even if a user closes their browser

* Pros:
    * Permanent and reliable form of storage. Unlike cookies, which can be deleted at any time under the user's discretion, data stored in JSON files remain in the server
    * JSON files can be accessed or opened using almost any program (e.g. Python, JavaScript, Excel etc). This means data can be shared easily across varying applications
    * Offers significant storage capacity
    * Because the file lives in the server, it allows one to collect and manage data on users (such as counting the amount of users using the web app, analysing patterns etc)

* Cons:
    * There is no way to automatically sync the data, meaning it requires manually writing the code to open() and load(), modify the dictionary/list, and dump() the file every time a change is made
    * Since the amount of data that can be stored in a JSON file is substantial, in the event that the file reaches a certain size, it can cause running the server to lag when parsing through it for a specific item
    * Risk of data corruption causing the entirety of the file and its content to be lost

|Task|Use Cookie/JSON file|Context|
|---|---|---|
|Login sessions|Cookie|Only requires short-term "live" data to identify the user|
|Leaderboard|JSON file|Because you need to be able to collectively record and save every user's score|

## CSS
* Cascading Style Sheets → A type of programming language used to style a web page. It describes the visual appearances of HTML pages and how elements are to be displayed. It defines styles including the design, layout, and variations in display of web pages.

|Term|Structure/Form|Context|
|---|---|---|
|Selector|.container|Represents the HTML element to be styled. The "id" or "class_" given to the element|
|Property|display:|The specific feature of the element that is to be changed|
|Value|grid;|The specific setting to be applied to the specified property. Always end with a semi-colon|
|Comment|/* ... */|A comment for humans or developers which the browser will ignore|

* Sample:

        .container {
            display: grid;
            grid-template-columns: 1fr auto 1fr; (here "1" is the size of the column)
            grid-template-areas:
                ". header btn_back"
                "journey journey journey"
                "table table summary"
                "table table .";
        }

* To target *id*: use the hash key "#"

        #header-main {
            background-color: blue;
            color: white;
        }

* Child selector (to target and style specified items within a div):

        #div > p {
            color: black;
        }

        For paragraphs inside div, set the text color to black

* Code to link CSS page to server:

        HTML: <link rel="stylesheet" href="static/style.css">

        PyHTML: Create the following helper function.

        def page_layout():
            css = p.html(
                p.head(
                    p.link(rel="stylesheet", href="static/style.css")
                )
            )
            return css


* Steps:
    1. Wrap elements to be styled in the server side using *p.div(class_="header")(p...)*. Div works to group things together so it can be organised and a style can be applied for the whole section at once. Assign a "class_='*name*'" to act as a unique identifier for the div
    2. Create a file called *style.css* to house the CSS code
    3. Link the CSS document to the server
    4. For each webpage, use a container to create a framework/skeleton on how the items should be displayed (e.g. using a header, body, footer)
    5. When using *grid*, *grid-template-columns:* signifies the size of each column (default is usually either 1fr (meaning only 1 column for the entire webpage) or 1fr auto 1fr (auto is used to stretch and automatically adjust the size of the middle part of the page as needed))
    6. For each of the areas identified in *grid-template-areas:*, you can adjust or modify each area using:

            .container div.*given class of div in server*{
                grid-area: *area name*;
                text-align: center;
                ...
            }

* CSS Box Terms:
    * Content → The content of the box, where text and images appear (innermost box)
    * Padding → Clears an area around the content. The padding is transparent (2nd layer)
    * Border → A border that goes around the padding and content (3rd layer)
    * Margin → Clears an area outside the border. The margin is transparent (outermost box)

* Examples:
    * Labels:

            label {
                font-family: Karla, sans-serif;
                font-size: 25px;
                text-align: left;
                font-weight: bold;
            }

            input[type=text],
            input[type=password], select {
                width: 440px;
                padding: 12px;
                margin-bottom: 10px;
                margin: auto;
                margin-bottom: 10px;
                display: block;
            }

            input[type=text],
            input[type=password] {
                font-family: Karla, sans-serif;
                font-size: large;
            }

            ::placeholder {
                color: darkgrey;
            }
    * Buttons:

            .btn_login {
                padding: 12px 199px;
                font-family: Karla, sans-serif;
                font-size: 16px;
                text-align: center;
                background-color: black;
                color: white;
                border-radius: 5px;
                transition: 0.3s;
            }

            .btn_login:hover {
                opacity: 0.8;
                cursor: pointer;
            }
    * Table:
            .abc_table table {
            width: 85%;
            margin-bottom: 40px;
            border-collapse: collapse;
            }

            .abc_table th {
                background-color: #f2f2f2;
                padding: 12px;
                border: 1px solid #ddd;
                text-align: left;
                height: 25px
            }

            .abc_table td {
                padding: 10px;
                border: 1px solid #ddd;
            }

## Heuristics
* Visibility of system status → The design should always keep users informed on what is going on. Predictable interactions work to create trust.
* User control and freedom → Having back buttons to prevent or undo unwanted actions fosters a sense of freedom and confidence. Allows users to remain in control of the system, especially if it clearly labelled and discoverable.
* Consistency and standards → Maintaining a sense of familiarity that meets user's expectations in using the application, especially using established industry conventions. Improves learnability and reduces the burden or users' cognitive load on learning how to navigate through the web app.
* Error prevention → Preventing issues from occurring in the first place. Eliminating error-prone conditions or checking for them.
* Recognition → Using visible field labels or menu items helps to promote recognition and offers help contextually.
* Aesthetic and minimal design → Interfaces should only contain relevant information because each extra unit of information competes with the relevant units of information and can diminish their overall visibility. Thus, making it important to keep the content and visual elements lean, specifically narrowed down to focus on the essentials.
