Repository: visualpython/visualpython (language: python)

Issue:
Devops for v2.4.5
1. Edit Frame - Add / Replace function
2. Add Help button for Pandas library (sequential updates for the other apps)
3. Fix Snippets initial issue


Discussion / hints:

Fix patch:
diff --git a/build.sh b/build.sh
index c394364e..e2ace824 100755
--- a/build.sh
+++ b/build.sh
@@ -11,8 +11,8 @@
 #=============================================================================
 # Set version and replace it
 #=============================================================================
-VP_ORG_VER=2.4.3
-VP_NEW_VER=2.4.4
+VP_ORG_VER=2.4.4
+VP_NEW_VER=2.4.5
 
 # update version info
 grep -REil "VP_ORG_VER=.+$" colab/build.colab.sh jupyterlab/build.jupyterlab.sh jupyternotebook/build.jupyternotebook.sh | xargs sed -i "s/VP_ORG_VER=.\+$/VP_ORG_VER=${VP_ORG_VER}/g"
diff --git a/colab/build.colab.sh b/colab/build.colab.sh
index 0fc663f4..dfd81e9a 100755
--- a/colab/build.colab.sh
+++ b/colab/build.colab.sh
@@ -11,8 +11,8 @@
 #=============================================================================
 # Replace Version
 #=============================================================================
-VP_ORG_VER=2.4.3
-VP_NEW_VER=2.4.4
+VP_ORG_VER=2.4.4
+VP_NEW_VER=2.4.5
 
 # update version info
 # update manifest version with new numbering for new version
diff --git a/colab/manifest.json b/colab/manifest.json
index 4024cf56..99d338e5 100644
--- a/colab/manifest.json
+++ b/colab/manifest.json
@@ -1,7 +1,7 @@
 {
     "name": "Visual Python for Colab",
     "description": "GUI-based Python code generator for Google Colab as an extension",
-    "version": "2.4.4",
+    "version": "2.4.5",
     "manifest_version": 3,
     "icons": {
         "48": "icon.png",
diff --git a/jupyterlab/build.jupyterlab.sh b/jupyterlab/build.jupyterlab.sh
index 9d43a172..57ad140b 100755
--- a/jupyterlab/build.jupyterlab.sh
+++ b/jupyterlab/build.jupyterlab.sh
@@ -11,8 +11,8 @@
 #=============================================================================
 # Replace Version and Basic Files
 #=============================================================================
-VP_ORG_VER=2.4.3
-VP_NEW_VER=2.4.4
+VP_ORG_VER=2.4.4
+VP_NEW_VER=2.4.5
 
 # update version info
 grep -REil "\"version\": \"${VP_ORG_VER}\"" package.json | xargs sed -i "s/\"version\": \"${VP_ORG_VER//\./\\.}\"/\"version\": \"${VP_NEW_VER}\"/g"
diff --git a/jupyterlab/package-lock.json b/jupyterlab/package-lock.json
index 1e440644..0488d89d 100644
--- a/jupyterlab/package-lock.json
+++ b/jupyterlab/package-lock.json
@@ -1,12 +1,12 @@
 {
   "name": "jupyterlab-visualpython",
-  "version": "2.4.3",
+  "version": "2.4.4",
   "lockfileVersion": 2,
   "requires": true,
   "packages": {
     "": {
       "name": "jupyterlab-visualpython",
-      "version": "2.4.3",
+      "version": "2.4.4",
       "license": "GPLv3 with Visual Python special exception",
       "dependencies": {
         "@jupyterlab/cells": "^3.5.2",
diff --git a/jupyterlab/package.json b/jupyterlab/package.json
index c0b0cba8..112513e3 100644
--- a/jupyterlab/package.json
+++ b/jupyterlab/package.json
@@ -1,6 +1,6 @@
 {
   "name": "jupyterlab-visualpython",
-  "version": "2.4.4",
+  "version": "2.4.5",
   "description": "GUI-based Python code generator for Jupyter Lab as an extension",
   "keywords": [
     "jupyter",
diff --git a/jupyterlab/pyproject.toml b/jupyterlab/pyproject.toml
index 48944c97..59d2c8e1 100644
--- a/jupyterlab/pyproject.toml
+++ b/jupyterlab/pyproject.toml
@@ -32,7 +32,7 @@ classifiers = [
     "Programming Language :: Python :: 3.9",
     "Programming Language :: Python :: 3.10",
 ]
-version = "2.4.4"
+version = "2.4.5"
 
 [project.license]
 file = "LICENSE"
@@ -92,7 +92,7 @@ file = [
 ]
 
 [tool.tbump.version]
-current = "2.4.4"
+current = "2.4.5"
 regex = "(?P<major>\\d+)\\.(?P<minor>\\d+)\\.(?P<patch>\\d+)((?P<channel>a|b|rc|.dev)(?P<release>\\d+))?"
 
 [tool.tbump.git]
diff --git a/jupyternotebook/build.jupyternotebook.sh b/jupyternotebook/build.jupyternotebook.sh
index d896b220..51ce6c85 100755
--- a/jupyternotebook/build.jupyternotebook.sh
+++ b/jupyternotebook/build.jupyternotebook.sh
@@ -11,8 +11,8 @@
 #=============================================================================
 # Replace Version and Basic Files
 #=============================================================================
-VP_ORG_VER=2.4.3
-VP_NEW_VER=2.4.4
+VP_ORG_VER=2.4.4
+VP_NEW_VER=2.4.5
 
 # update version info
 grep -REil ${VP_ORG_VER//\./\\.} setup.py visualpython/js/com/com_Config.js visualpython/js/com/com_Const.js | xargs sed -i --follow-symlinks "s/${VP_ORG_VER//\./\\.}/${VP_NEW_VER}/g"
diff --git a/jupyternotebook/setup.py b/jupyternotebook/setup.py
index f41228e7..3767ab7a 100644
--- a/jupyternotebook/setup.py
+++ b/jupyternotebook/setup.py
@@ -10,7 +10,7 @@
 
 setup(
     name             = name,
-    version          = '2.4.4',
+    version          = '2.4.5',
     packages         = find_packages(),
     package_data     = {"": ["*"], 'visualpython' : ['visualpython.yaml', 'README.md']},
     scripts          = ['visualpython/bin/visualpy', 'visualpython/bin/visualpy.bat'],
diff --git a/visualpython/css/component/popupComponent.css b/visualpython/css/component/popupComponent.css
index 6aa3efbf..ca2f9736 100644
--- a/visualpython/css/component/popupComponent.css
+++ b/visualpython/css/component/popupComponent.css
@@ -191,11 +191,21 @@
     float: left;
     margin-top: 9px;
     margin-left: 10px;
+    line-height: 20px;
 }
 .vp-popup-button[data-type="data"] {
     float: left;
     margin-top: 9px;
     margin-left: 10px;
+    line-height: 20px;
+}
+.vp-popup-button[data-type="help"] {
+    float: left;
+    margin-top: 9px;
+    margin-left: 10px;
+    background-color: #F9B52A;
+    color: var(--vp-background-color);
+    line-height: 20px;
 }
 .vp-popup-button[data-type="cancel"] {
     float: right;
diff --git a/visualpython/css/m_apps/frame.css b/visualpython/css/m_apps/frame.css
index 905150d7..0731b958 100644
--- a/visualpython/css/m_apps/frame.css
+++ b/visualpython/css/m_apps/frame.css
@@ -279,6 +279,9 @@
 .vp-inner-popup-apply-column {
     width: 153px;
 }
+.vp-inner-popup-apply-target-name {
+    width: 115px;
+}
 .vp-inner-popup-sortby {
     border: 0.25px solid var(--vp-border-gray-color);
     width: 160px;
diff --git a/visualpython/css/root.css b/visualpython/css/root.css
index 55a7a79f..4122a5bd 100644
--- a/visualpython/css/root.css
+++ b/visualpython/css/root.css
@@ -586,6 +586,11 @@ hr.vp-extra-menu-line {
 .vp-inner-popup-body .w70 {
     width: 70px !important;
 }
+#vp_wrapper .w60,
+.vp-popup-frame .w60,
+.vp-inner-popup-body .w60 {
+    width: 60px !important;
+}
 #vp_wrapper .w50,
 .vp-popup-frame .w50,
 .vp-inner-popup-body .w50 {
diff --git a/visualpython/data/m_library/pandasLibrary.js b/visualpython/data/m_library/pandasLibrary.js
index 0646205b..b18ed0c9 100644
--- a/visualpython/data/m_library/pandasLibrary.js
+++ b/visualpython/data/m_library/pandasLibrary.js
@@ -32,6 +32,7 @@ define([
         "name": "Series",
         "library": "pandas",
         "description": "1 dimension array with same data types",
+        "docs": "https://pandas.pydata.org/docs/reference/api/pandas.Series.html",
         "code": "${o0} = pd.Series(${i0}${index}${name})",
         "options": [
           {
@@ -64,6 +65,7 @@ define([
         "name": "DataFrame",
         "library": "pandas",
         "description": "2 dimension data table type pandas variable",
+        "docs": "https://pandas.pydata.org/docs/reference/api/pandas.DataFrame.html",
         "code": "${o0} = pd.DataFrame(${i0}${index}${columns})",
         "options": [
           {
@@ -98,6 +100,7 @@ define([
         "name": "Index",
         "library": "pandas",
         "description": "Create index object",
+        "docs": "https://pandas.pydata.org/docs/reference/api/pandas.Index.html",
         "code": "${o0} = pd.Index(${data}${dtype}${copy}${name}${tupleize_cols})",
         "options": [
           {
@@ -162,6 +165,7 @@ define([
         "name": "Read CSV",
         "library": "pandas",
         "description": "",
+        "docs": "https://pandas.pydata.org/docs/reference/api/pandas.read_csv.html",
         "code": "${o0} = pd.read_csv(${i0}${encoding}${header}${sep}${names}${usecols}${index_col}${na_values}${skiprows}${nrows}${chunksize}${etc})",
         "options": [
           {
@@ -250,6 +254,7 @@ define([
         "name": "To CSV",
         "library": "pandas",
         "description": "dataframe to csv",
+        "docs": "https://pandas.pydata.org/docs/reference/api/pandas.DataFrame.to_csv.html",
         "code": "${i0}.to_csv(${i1}${encoding}${header}${index}${sep}${na_rep}${columns}${etc})",
         "options": [
           {
@@ -320,6 +325,7 @@ define([
         "name": "Merge",
         "library": "pandas",
         "description": "Merge 2 objects",
+        "docs": "https://pandas.pydata.org/docs/reference/api/pandas.merge.html",
         "code": "${o0} = pd.merge(${i0}, ${i1}${left_on}${right_on}${how}${sort})",
         "options": [
           {
@@ -390,6 +396,7 @@ define([
         "name": "Join",
         "library": "pandas",
         "description": "Merge multiple objects",
+        "docs": "https://pandas.pydata.org/docs/reference/api/pandas.DataFrame.join.html",
         "code": "${o0} = ${i0}.join(${i1}${on}${how}${sort}${lsuffix}${rsuffix})",
         "options": [
           {
@@ -469,6 +476,7 @@ define([
         "name": "Concat",
         "library": "pandas",
         "description": "Merge multiple objects",
+        "docs": "https://pandas.pydata.org/docs/reference/api/pandas.concat.html",
         "code": "${o0} = pd.concat([${i0}]${index}${axis}${sort}${join})",
         "options": [
           {
@@ -540,6 +548,7 @@ define([
         "name": "Sort By Index",
         "library": "pandas",
         "description": "Sort by index",
+        "docs": "https://pandas.pydata.org/docs/reference/api/pandas.DataFrame.sort_index.html",
         "code": "${o0} = ${i0}.sort_index(${axis}${ascending}${inplace}${kind})",
         "options": [
           {
@@ -624,6 +633,7 @@ define([
         "name": "Group By",
         "library": "pandas",
         "description": "Group DataFrame/Series",
+        "docs": "https://pandas.pydata.org/docs/reference/api/pandas.DataFrame.groupby.html",
         "code": "${o0} = ${i0}.groupby(${level}${axis}${sort}${as_index})",
         "options": [
           {
@@ -692,6 +702,7 @@ define([
         "name": "Period",
         "library": "pandas",
         "description": "Create Period object",
+        "docs": "https://pandas.pydata.org/docs/reference/api/pandas.Period.html",
         "code": "${o0} = pd.Period(${i0}${freq}${year}${month}${day})",
         "options": [
           {
@@ -759,6 +770,7 @@ define([
         "name": "Drop NA",
         "library": "pandas",
         "description": "",
+        "docs": "https://pandas.pydata.org/docs/reference/api/pandas.DataFrame.dropna.html",
         "code": "${o0} = ${i0}.dropna(${axis}${how}${thresh})",
         "options": [
           {
@@ -823,6 +835,7 @@ define([
         "name": "Fill NA",
         "library": "pandas",
         "description": "replace null using value",
+        "docs": "https://pandas.pydata.org/docs/reference/api/pandas.DataFrame.fillna.html",
         "code": "${o0} = ${i0}.fillna(${value}${axis}${method}${inplace}${limit})",
         "options": [
           {
@@ -902,6 +915,7 @@ define([
         "name": "Get Duplicates",
         "library": "pandas",
         "description": "Get duplicates",
+        "docs": "https://pandas.pydata.org/docs/reference/api/pandas.DataFrame.duplicated.html",
         "code": "${o0} = ${i0}.duplicated(${keep})",
         "options": [
           {
@@ -986,6 +1000,7 @@ define([
         "name": "Scala Replace",
         "library": "pandas",
         "description": "Replace scala value",
+        "docs": "https://pandas.pydata.org/docs/reference/api/pandas.DataFrame.replace.html",
         "code": "${o0} = ${i0}.replace(${to_replace}${value}${method})",
         "options": [
           {
@@ -1038,6 +1053,7 @@ define([
         "name": "List-like Replace",
         "library": "pandas",
         "description": "Replace values using list",
+        "docs": "https://pandas.pydata.org/docs/reference/api/pandas.DataFrame.replace.html",
         "code": "${o0} = ${i0}.replace(${to_replace}${value}${method})",
         "options": [
           {
@@ -1090,6 +1106,7 @@ define([
         "name": "Dict-like Replace",
         "library": "pandas",
         "description": "Replace values using dictionary",
+        "docs": "https://pandas.pydata.org/docs/reference/api/pandas.DataFrame.replace.html",
         "code": "${o0} = ${i0}.replace(${to_replace}${value}${method})",
         "options": [
           {
@@ -1142,6 +1159,7 @@ define([
         "name": "Regular Expression Replace",
         "library": "pandas",
         "description": "",
+        "docs": "https://pandas.pydata.org/docs/reference/api/pandas.DataFrame.replace.html",
         "code": "${o0} = ${i0}.replace(${to_replace}${value}${method}${regex})",
         "options": [
           {
@@ -1203,6 +1221,7 @@ define([
         "name": "Sum",
         "library": "pandas",
         "description": "",
+        "docs": "https://pandas.pydata.org/docs/reference/api/pandas.DataFrame.sum.html",
         "code": "${o0} = ${i0}.sum(${axis}${skipna}${level})",
         "options": [
           {
@@ -1262,6 +1281,7 @@ define([
         "name": "Mean",
         "library": "pandas",
         "description": "",
+        "docs": "https://pandas.pydata.org/docs/reference/api/pandas.DataFrame.mean.html",
         "code": "${o0} = ${i0}.mean(${axis}${skipna}${level})",
         "options": [
           {
@@ -1321,6 +1341,7 @@ define([
         "name": "Count",
         "library": "pandas",
         "description": "Count except NA values",
+        "docs": "https://pandas.pydata.org/docs/reference/api/pandas.DataFrame.count.html",
         "code": "${o0} = ${i0}.count(${axis}${skipna}${level})",
         "options": [
           {
@@ -1380,6 +1401,7 @@ define([
         "name": "Max",
         "library": "pandas",
         "description": "",
+        "docs": "https://pandas.pydata.org/docs/reference/api/pandas.DataFrame.max.html",
         "code": "${o0} = ${i0}.max(${axis}${skipna}${level})",
         "options": [
           {
@@ -1439,6 +1461,7 @@ define([
         "name": "Min",
         "library": "pandas",
         "description": "",
+        "docs": "https://pandas.pydata.org/docs/reference/api/pandas.DataFrame.min.html",
         "code": "${o0} = ${i0}.min(${axis}${skipna}${level})",
         "options": [
           {
@@ -1498,6 +1521,7 @@ define([
         "name": "Median",
         "library": "pandas",
         "description": "Median(50%)",
+        "docs": "https://pandas.pydata.org/docs/reference/api/pandas.DataFrame.median.html",
         "code": "${o0} = ${i0}.median(${axis}${skipna}${level}${numeric_only})",
         "options": [
           {
@@ -1574,6 +1598,7 @@ define([
         "name": "Std",
         "library": "pandas",
         "description": "",
+        "docs": "https://pandas.pydata.org/docs/reference/api/pandas.DataFrame.std.html",
         "code": "${o0} = ${i0}.std(${axis}${skipna}${level}${numeric_only})",
         "options": [
           {
@@ -1650,6 +1675,7 @@ define([
         "name": "Quantile",
         "library": "pandas",
         "description": "Calculate quantile between 0 and 1",
+        "docs": "https://pandas.pydata.org/docs/reference/api/pandas.DataFrame.quantile.html",
         "code": "${o0} = ${i0}.quantile(${q}${axis}${numeric_only}${interpolation})",
         "options": [
           {
@@ -1735,6 +1761,7 @@ define([
         "name": "Drop Row/Column",
         "library": "pandas",
         "description": "Drop row and column",
+        "docs": "https://pandas.pydata.org/docs/reference/api/pandas.DataFrame.drop.html",
         "code": "${o0} = ${i0}.drop(${i1}${axis})",
         "options": [
           {
@@ -1791,6 +1818,7 @@ define([
         "name": "date_range",
         "library": "pandas",
         "description": "Create DatetimeIndex type timestamp",
+        "docs": "https://pandas.pydata.org/docs/reference/api/pandas.date_range.html",
         "code": "${o0} = pd.date_range(${start}${end}${periods}${freq})",
         "options": [
           {
@@ -1862,6 +1890,7 @@ define([
         "name": "Sort By Values",
         "library": "pandas",
         "description": "",
+        "docs": "https://pandas.pydata.org/docs/reference/api/pandas.DataFrame.sort_values.html",
         "code": "${o0} = ${i0}.sort_values(${by}${axis}${ascending}${inplace}${kind})",
         "options": [
           {
@@ -1946,6 +1975,7 @@ define([
         "name": "Is Null",
         "library": "pandas",
         "description": "Find null",
+        "docs": "https://pandas.pydata.org/docs/reference/api/pandas.isnull.html",
         "code": "${o0} = pd.isnull(${i0})",
         "options": [
           {
@@ -1974,6 +2004,7 @@ define([
         "name": "Not Null",
         "library": "pandas",
         "description": "Find not null",
+        "docs": "https://pandas.pydata.org/docs/reference/api/pandas.notnull.html",
         "code": "${o0} = pd.notnull(${i0})",
         "options": [
           {
@@ -2002,6 +2033,7 @@ define([
         "name": "Transpose",
         "library": "pandas",
         "description": "Transpose row and column",
+        "docs": "https://pandas.pydata.org/docs/reference/api/pandas.DataFrame.T.html",
         "code": "${o0} = ${i0}.T",
         "options": [
           {
@@ -2031,6 +2063,7 @@ define([
         "name": "Get columns",
         "library": "pandas",
         "description": "",
+        "docs": "https://pandas.pydata.org/docs/reference/api/pandas.DataFrame.columns.html",
         "code": "${o0} = ${i0}.columns",
         "options": [
           {
@@ -2375,6 +2408,7 @@ define([
         "name": "Unique",
         "library": "pandas",
         "description": "",
+        "docs": "https://pandas.pydata.org/docs/reference/api/pandas.Series.unique.html",
         "code": "${o0} = ${i0}.unique()",
         "options": [
           {
diff --git a/visualpython/html/component/helpViewer.html b/visualpython/html/component/helpViewer.html
new file mode 100644
index 00000000..295370ac
--- /dev/null
+++ b/visualpython/html/component/helpViewer.html
@@ -0,0 +1,41 @@
+<!--
+    Project Name    : Visual Python
+    Description     : GUI-based Python code generator
+    File Name       : helpViewer.html
+    Author          : Black Logic
+    Note            : Help viewer
+    License         : GNU GPLv3 with Visual Python special exception
+    Date            : 2023. 07. 13
+    Change Date     : 
+-->
+<!-- use body tag to strip comments out on requirejs/text plugin -->
+<body>
+    <div class="vp-popup-frame">
+        <div class="vp-popup-header">
+            <!-- CHROME: title is a handler for draggable, but for some reason label tag's width is not controllable. changed tag: label -> div -->
+            <div class="vp-popup-title">
+                <!-- Title -->
+                Help viewer
+            </div>
+            <div class="vp-popup-maximize" title="Maximize this popup"></div>
+            <div class="vp-popup-return" title="Return size of this popup"></div>
+            <div class="vp-popup-close" title="Close popup"></div>
+        </div>
+        <div class="vp-popup-body vp-scrollbar">
+            <!-- Body -->
+            <div class="vp-popup-content">
+                <!-- <textarea id="helpContent" class="wp100"></textarea> -->
+                <pre id="helpContent">
+
+                </pre>
+            </div>
+        </div>
+        <div class="vp-popup-footer">
+            <!-- Footer -->
+            <!-- Button box -->
+            <div class="vp-popup-button-box">
+                <button type="button" class="vp-button vp-popup-button" data-type="cancel">Cancel</button>
+            </div>
+        </div>
+    </div>
+</body>
\ No newline at end of file
diff --git a/visualpython/html/component/popupComponent.html b/visualpython/html/component/popupComponent.html
index 7fc8ce52..64b0f58d 100644
--- a/visualpython/html/component/popupComponent.html
+++ b/visualpython/html/component/popupComponent.html
@@ -102,6 +102,7 @@
             <div class="vp-popup-button-box">
                 <button type="button" class="vp-button vp-popup-button" data-type="code">Code view</button>
                 <button type="button" class="vp-button vp-popup-button" data-type="data">Data view</button>
+                <button type="button" class="vp-button vp-popup-button" data-type="help">Help</button>
 
                 <div class="vp-popup-runadd-box">
                     <button type="button" class="vp-button activated vp-popup-button" data-type="run" title="Save to block & Run cell">Run</button>
diff --git a/visualpython/js/com/com_Config.js b/visualpython/js/com/com_Config.js
index 868d5e98..bbc6fb83 100644
--- a/visualpython/js/com/com_Config.js
+++ b/visualpython/js/com/com_Config.js
@@ -1052,7 +1052,7 @@ define([
     /**
      * Version
      */
-    Config.version = "2.4.4";
+    Config.version = "2.4.5";
 
     /**
      * Type of mode
diff --git a/visualpython/js/com/com_Const.js b/visualpython/js/com/com_Const.js
index b4f72374..b7e9d7cd 100644
--- a/visualpython/js/com/com_Const.js
+++ b/visualpython/js/com/com_Const.js
@@ -19,7 +19,7 @@ define ([
     class Constants { }
 
     Constants.TOOLBAR_BTN_INFO = {
-        HELP: "Visual Python 2.4.4"
+        HELP: "Visual Python 2.4.5"
         , ICON: "vp-main-icon"
         , ID: "vpBtnToggle"
         , NAME: "toggle-vp"
diff --git a/visualpython/js/com/component/HelpViewer.js b/visualpython/js/com/component/HelpViewer.js
new file mode 100644
index 00000000..ac8a8e68
--- /dev/null
+++ b/visualpython/js/com/component/HelpViewer.js
@@ -0,0 +1,217 @@
+/*
+ *    Project Name    : Visual Python
+ *    Description     : GUI-based Python code generator
+ *    File Name       : HelpViewer.js
+ *    Author          : Black Logic
+ *    Note            : Component > HelpViewer
+ *    License         : GNU GPLv3 with Visual Python special exception
+ *    Date            : 2023. 07. 13
+ *    Change Date     :
+ */
+//============================================================================
+// [CLASS] HelpViewer
+//============================================================================
+define([
+    __VP_TEXT_LOADER__('vp_base/html/component/helpViewer.html'), // INTEGRATION: unified version of text loader
+    __VP_CSS_LOADER__('vp_base/css/component/popupComponent'), // INTEGRATION: unified version of css loader
+    'vp_base/js/com/com_util',
+    'vp_base/js/com/com_Const',
+    'vp_base/js/com/com_String',
+    'vp_base/js/com/component/Component',
+    'vp_base/js/com/component/LoadingSpinner'
+], function(hvHtml, ppCss, com_util, com_Const, com_String, Component, LoadingSpinner) {
+
+    /**
+     * HelpViewer
+     */
+    class HelpViewer extends Component {
+        constructor() {
+            super($(vpConfig.parentSelector), {}, {});
+        }
+
+        _init() {
+            this.position = {
+                right: 10, top: 120
+            };
+            this.size = {
+                width: 500,
+                height: 500
+            };
+        }
+
+
+        _bindEvent() {
+            super._bindEvent();
+            /** Implement binding events */
+            let that = this;
+
+            $(that.wrapSelector('.vp-popup-maximize')).on('click', function() {
+                // save position
+                that.position = $(that.wrapSelector()).position();
+                // save size
+                that.size = {
+                    width: $(that.wrapSelector()).width(),
+                    height: $(that.wrapSelector()).height()
+                }
+                // maximize popup
+                $(that.wrapSelector()).css({
+                    width: '100%',
+                    height: '100%',
+                    top: 0,
+                    left: 0
+                });
+                // show / hide buttons
+                $(this).hide();
+                $(that.wrapSelector('.vp-popup-return')).show();
+            });
+
+            // Return operation
+            $(this.wrapSelector('.vp-popup-return')).on('click', function(evt) {
+                // return size
+                $(that.wrapSelector()).css({
+                    width: that.size.width + 'px',
+                    height: that.size.height + 'px',
+                    top: that.position.top,
+                    left: that.position.left
+                });
+                // show / hide buttons
+                $(this).hide();
+                $(that.wrapSelector('.vp-popup-maximize')).show();
+            });
+
+            $(that.wrapSelector('.vp-popup-close')).on('click', function() {
+                that.remove();
+            });
+
+            $(that.wrapSelector('.vp-popup-button')).on('click', function() {
+                var btnType = $(this).data('type');
+                switch(btnType) {
+                    case 'cancel':
+                        that.remove();
+                        break;
+                }
+            });
+
+            // Focus recognization
+            $(this.wrapSelector()).on('click', function() {
+                that.focus();
+            });
+        }
+
+        _bindDraggable() {
+            var that = this;
+            let containment = 'body';
+            if (vpConfig.extensionType === 'lab' || vpConfig.extensionType === 'lite') {
+                containment = '#main';
+            }
+            $(this.wrapSelector()).draggable({
+                handle: '.vp-popup-title',
+                containment: containment,
+                start: function(evt, ui) {
+                    // check focused
+                    $(that.eventTarget).trigger({
+                        type: 'focus_option_page',
+                        component: that
+                    });
+                }
+            });
+        }
+
+        _bindResizable() {
+            let that = this;
+            $(this.wrapSelector()).resizable({
+                handles: 'all',
+                start: function(evt, ui) {
+                    // show / hide buttons
+                    $(that.wrapSelector('.vp-popup-return')).hide();
+                    $(that.wrapSelector('.vp-popup-maximize')).show();
+                }
+            });
+        }
+
+        template() {
+            this.$pageDom = $(hvHtml);
+
+            return this.$pageDom;
+        }
+
+        render() {
+            super.render();
+
+            // set detailed size
+            $(this.wrapSelector()).css({
+                width: this.size.width + 'px',
+                height: this.size.height + 'px'
+            });
+
+            // position
+            $(this.wrapSelector()).css({ top: this.position.top, right: this.position.right });
+
+            this._bindDraggable();
+            this._bindResizable();
+        }
+
+        wrapSelector(selector='') {
+            var sbSelector = new com_String();
+            var cnt = arguments.length;
+            if (cnt < 2) {
+                // if there's no more arguments
+                sbSelector.appendFormat(".vp-popup-frame.{0} {1}", this.uuid, selector);
+            } else {
+                // if there's more arguments
+                sbSelector.appendFormat(".vp-popup-frame.{0}", this.uuid);
+                for (var idx = 0; idx < cnt; idx++) {
+                    sbSelector.appendFormat(" {0}", arguments[idx]);
+                }
+            }
+            return sbSelector.toString();
+        }
+
+        open(content, useHelp=true) {
+            this.show();
+
+            let that = this;
+
+            let code = content;
+            if (useHelp === true) {
+                code = `print(help(${content}))`;
+            }
+
+            let loadingSpinner = new LoadingSpinner($(this.wrapSelector('.vp-popup-body')));
+            vpKernel.execute(code).then(function(resultObj) {
+                let { result } = resultObj;
+
+                $(that.wrapSelector('#helpContent')).text(result);
+
+            }).catch(function(err) {
+                vpLog.display(VP_LOG_TYPE.ERROR, err);
+            }).finally(function() {
+                loadingSpinner.remove();
+            });
+
+            this.focus();
+        }
+
+        generateCode() {
+            return '';
+        }
+
+        show() {
+            $(this.wrapSelector()).show();
+        }
+
+        remove() {
+            $(this.wrapSelector()).remove();
+        }
+
+        focus() {
+            $('.vp-popup-frame').removeClass('vp-focused');
+            $('.vp-popup-frame').css({ 'z-index': 1200 });
+            $(this.wrapSelector()).addClass('vp-focused');
+            $(this.wrapSelector()).css({ 'z-index': 1205 }); // move forward
+        }
+
+    }
+
+    return HelpViewer;
+});
\ No newline at end of file
diff --git a/visualpython/js/com/component/LibraryComponent.js b/visualpython/js/com/component/LibraryComponent.js
index d7d0ff56..e02c31f2 100644
--- a/visualpython/js/com/component/LibraryComponent.js
+++ b/visualpython/js/com/component/LibraryComponent.js
@@ -30,6 +30,7 @@ define([
             /** Write codes executed before rendering */
             this.config.dataview = false;
             this.config.sizeLevel = 1;
+            this.config.helpview = true;
             
             this.packageId = this.id;
             // deep copy package info
@@ -46,7 +47,42 @@ define([
                 return;
             }
             this.config.checkModules = ['pd'];
-            this.config.docs = 'https://pandas.pydata.org/docs/reference/index.html';
+            
+            // set docs link
+            if (this.package.docs === undefined) {
+                let docsLink = 'https://pandas.pydata.org/docs/reference/api/pandas.';
+                let docsMatchObj = this.package.code.match(/\= (.+)\.(.+)\(/);
+                if (docsMatchObj) {
+                    let targetType = docsMatchObj[1]; // ${i0} or pd
+                    let method = docsMatchObj[2];
+                    if (targetType === 'pd') {
+                        docsLink += method + '.html';
+                    } else {
+                        docsLink += 'DataFrame.' + method + '.html';
+                    }
+                    this.config.docs = docsLink;
+                } else {
+                    this.config.docs = 'https://pandas.pydata.org/docs/reference/index.html';
+                }
+            } else {
+                this.config.docs = this.package.docs;
+            }
+
+            // set helpview content
+            let helpMatchObj = this.package.code.match(/\= (.+)\.(.+)\(/);
+            if (helpMatchObj) {
+                let helpContent = '';
+                let targetType = helpMatchObj[1]; // ${i0} or pd
+                let method = helpMatchObj[2];
+                if (targetType === 'pd') {
+                    helpContent += '_vp_pd.' + method;
+                } else {
+                    helpContent += '_vp_pd.' + 'DataFrame.' + method;
+                }
+                this.config.helpInfo.content = helpContent;
+            } else {
+                this.config.helpview = false;
+            }
 
             vpLog.display(VP_LOG_TYPE.DEVELOP, 'loading state', this.state);
         }
diff --git a/visualpython/js/com/component/NumpyComponent.js b/visualpython/js/com/component/NumpyComponent.js
index a8b4b7e7..d4bb217f 100644
--- a/visualpython/js/com/component/NumpyComponent.js
+++ b/visualpython/js/com/component/NumpyComponent.js
@@ -53,6 +53,16 @@ define([
             }
             this.config.checkModules = ['np'];
 
+            if (this.package.docs === undefined) {
+                if (packageName == 'numpy') {
+                    this.config.docs = 'https://numpy.org/doc/';
+                } else if (packageName == 'python') {
+                    this.config.docs = 'https://docs.python.org/3.11/library/index.html';
+                }
+            } else {
+                this.config.docs = this.package.docs;
+            }
+
             vpLog.display(VP_LOG_TYPE.DEVELOP, 'loading state', this.state);
         }
 
diff --git a/visualpython/js/com/component/PopupComponent.js b/visualpython/js/com/component/PopupComponent.js
index 89a34983..90d79712 100644
--- a/visualpython/js/com/component/PopupComponent.js
+++ b/visualpython/js/com/component/PopupComponent.js
@@ -62,6 +62,7 @@ define([
     '../com_interface',
     './Component',
     './DataSelector',
+    './HelpViewer',
 
     /** codemirror */
     'codemirror/lib/codemirror',
@@ -70,7 +71,7 @@ define([
     'codemirror/addon/display/autorefresh',
     // 'notebook/js/codemirror-ipython' // LAB: disabled to avoid error FIXME:
 ], function(popupComponentHtml, popupComponentCss
-    , com_util, com_Const, com_String, com_interface, Component, DataSelector, codemirror
+    , com_util, com_Const, com_String, com_interface, Component, DataSelector, HelpViewer, codemirror
 ) {
     'use strict';
 
@@ -107,6 +108,7 @@ define([
                 // show view box
                 codeview: true, 
                 dataview: true,
+                helpview: false,
                 // show footer
                 runButton: true,
                 footer: true,
@@ -115,6 +117,10 @@ define([
                 saveOnly: false, // apply mode
                 checkModules: [], // module aliases or function names
                 docs: 'https://visual-python.gitbook.io/docs/getting-started/welcome-to-visual-python',
+                helpInfo: {
+                    content: '', // method to show using help() ex) pd.DataFrame
+                    useHelp: true // custom text to show on help viewer
+                },
                 ...restConfig
             };
 
@@ -145,8 +151,9 @@ define([
             }
 
             this.cmCodeview = null;
-
             this.cmCodeList = [];
+
+            this.helpViewer = undefined;
         }
 
         wrapSelector(selector='') {
@@ -426,6 +433,14 @@ define([
                         }
                         evt.stopPropagation();
                         break;
+                    case 'help':
+                        if ($(that.wrapSelector('.vp-popup-dataview-box')).is(':hidden')) {
+                            that.openView('help');
+                        } else {
+                            that.closeView('help');
+                        }
+                        evt.stopPropagation();
+                        break;
                     case 'cancel':
                         if (that.getTaskType() === 'task') {
                             $(that.eventTarget).trigger({
@@ -583,7 +598,7 @@ define([
 
             let { 
                 installButton, importButton, packageButton, 
-                codeview, dataview, runButton, footer, 
+                codeview, dataview, helpview, runButton, footer, 
                 sizeLevel, position, docs
             } = this.config;
 
@@ -619,6 +634,9 @@ define([
             if (!dataview) {
                 $(this.wrapSelector('.vp-popup-button[data-type="data"]')).hide();
             }
+            if (!helpview) {
+                $(this.wrapSelector('.vp-popup-button[data-type="help"]')).hide();
+            }
 
             // run button
             if (!runButton) {
@@ -919,6 +937,10 @@ define([
             return true;
         }
 
+        _beforeOpen() {
+            /** Implementation needed - Generated on clicking Install Package button */
+        }
+
         /**
          * Open popup
          * - show popup
@@ -929,6 +951,7 @@ define([
             vpLog.display(VP_LOG_TYPE.DEVELOP, 'open popup', this);
             this.loadState();
             
+            this._beforeOpen();
             this.show();
 
             // set popup position if its top-left side is outside of view
@@ -982,6 +1005,7 @@ define([
         remove() {
             vpLog.display(VP_LOG_TYPE.DEVELOP, 'remove popup', this);
             this._unbindEvent();
+            this.closeHelpView();
             $(this.wrapSelector()).remove();
         }
 
@@ -1014,6 +1038,7 @@ define([
         }
 
         hide() {
+            this.closeHelpView();
             this.taskItem && this.taskItem.blurItem();
             $(this.wrapSelector()).hide();
         }
@@ -1052,7 +1077,7 @@ define([
          * @param {*} viewType code / data
          */
         openView(viewType) {
-            if (viewType == 'code') {
+            if (viewType === 'code') {
                 this.saveState();
                 var code = this.generateCode();
                 let codeText = '';
@@ -1069,18 +1094,53 @@ define([
                     that.cmCodeview.refresh();
                 }, 1);
                 $(this.wrapSelector('.vp-popup-dataview-box')).hide();
-            } else {
+                $(this.wrapSelector('.vp-popup-codeview-box')).show();
+            } else if (viewType === 'data') {
                 this.renderDataView();
                 $(this.wrapSelector('.vp-popup-codeview-box')).hide();
+                $(this.wrapSelector('.vp-popup-dataview-box')).show();
+            } else if (viewType === 'help') {
+                $(this.wrapSelector('.vp-popup-codeview-box')).hide();
+                $(this.wrapSelector('.vp-popup-dataview-box')).hide();
+                this.openHelpView();
             }
-
-            $(this.wrapSelector('.vp-popup-'+viewType+'view-box')).show();
         }
 
         closeView(viewType) {
             $(this.wrapSelector('.vp-popup-'+viewType+'view-box')).hide();
         }
 
+        /**
+         * Open HelpViewer
+         * - only one helpviewer at once
+         */
+        openHelpView() {
+            this.closeHelpView();
+            this.helpViewer = new HelpViewer();
+            this.helpViewer.open(this.config.helpInfo.content, this.config.helpInfo.useHelp);
+        }
+
+        /**
+         * Close helpViewer
+         */
+        closeHelpView() {
+            if (this.helpViewer !== undefined) {
+                this.helpViewer.remove();
+            }
+        }
+
+        /**
+         * Set HelpViewer content
+         * @param {string} content 
+         * @param {boolean} useHelp 
+         */
+        setHelpContent(content, useHelp=true) {
+            this.config.helpInfo = {
+                content: content,
+                useHelp: useHelp
+            };
+        }
+
         /**
          * Open inner popup box
          */
diff --git a/visualpython/js/m_apps/Bind.js b/visualpython/js/m_apps/Bind.js
index 24d882f0..8b19c96a 100644
--- a/visualpython/js/m_apps/Bind.js
+++ b/visualpython/js/m_apps/Bind.js
@@ -31,6 +31,7 @@ define([
             /** Write codes executed before rendering */
             this.config.sizeLevel = 2;
             this.config.checkModules = ['pd'];
+            this.config.helpview = true;
 
             this.howList = [
                 { label: 'Inner', value: 'inner', desc: 'Inner join' },
@@ -127,6 +128,8 @@ define([
                 // clear user option
                 $(that.wrapSelector('#vp_bdUserOption')).val('');
                 that.state.userOption = '';
+
+                that.setHelpContent('_vp_pd.' + type);
             });
 
             //====================================================================
@@ -392,6 +395,8 @@ define([
                 $(this.wrapSelector('#vp_bdWithoutColumn')).hide();
             }
 
+            this.setHelpContent('_vp_pd.' + this.state.type);
+
             this.loadVariableList();
         }
 
diff --git a/visualpython/js/m_apps/Frame.js b/visualpython/js/m_apps/Frame.js
index 44d4727c..e8b98611 100644
--- a/visualpython/js/m_apps/Frame.js
+++ b/visualpython/js/m_apps/Frame.js
@@ -786,6 +786,11 @@ define([
                         var tab = $(this).val();
                         $(that.wrapSelector('.vp-inner-popup-tab')).hide();
                         $(that.wrapSelector('.vp-inner-popup-tab.' + tab)).show();
+
+                        if (tab === 'apply') {
+                            let colName = $(that.wrapSelector('.vp-inner-popup-apply-column option:selected')).text();
+                            $(that.wrapSelector('.vp-inner-popup-apply-target-name')).text(colName);
+                        }
                     });
             
                     // 2-1. hide column selection box
@@ -813,57 +818,6 @@ define([
                         $(this).closest('tr').find('.vp-inner-popup-vartype-box').hide();
                         $(this).closest('tr').find('.vp-inner-popup-vartype-box.' + type).show();
                     });
-
-                    // 4. apply
-                    $(this.wrapSelector('.vp-inner-popup-apply-column')).on('change', function() {
-                        // TODO: change apply-condition (save value)
-
-                    });
-
-                    $(this.wrapSelector('.vp-inner-popup-toggle-else')).on('click', function() {
-                        // toggle else on/off
-                        let elseOn = $(this).attr('data-else'); // on / off
-                        if (elseOn === 'off') {
-                            // off -> on
-                            $(this).attr('data-else', 'on');
-                            $(this).text('Else off');
-                            $(that.wrapSelector('.vp-inner-popup-apply-else-value')).prop('disabled', false);
-                            $(that.wrapSelector('.vp-inner-popup-apply-else-usetext')).prop('disabled', false);
-                        } else {
-                            // on -> off
-                            $(this).attr('data-else', 'off');
-                            $(this).text('Else on');
-                            $(that.wrapSelector('.vp-inner-popup-apply-else-value')).prop('disabled', true);
-                            $(that.wrapSelector('.vp-inner-popup-apply-else-usetext')).prop('disabled', true);
-                        }
-                    });
-
-                    $(this.wrapSelector('.vp-inner-popup-add-case')).on('click', function() {
-                        // add case
-                        $(this).parent().find('.vp-inner-popup-apply-case-box').append($(that.templateForApplyCase()));
-                    });
-
-                    $(document).off('click', this.wrapSelector('.vp-inner-popup-apply-add-cond'));
-                    $(document).on('click', this.wrapSelector('.vp-inner-popup-apply-add-cond'), function() {
-                        // add condition
-                        $(this).parent().find('.vp-inner-popup-apply-cond-box').append($(that.templateForApplyCondition()));
-                        // show operator except last operator
-                        $(this).parent().find('.vp-inner-popup-apply-oper-connect:not(:last)').show();
-                    });
-
-                    $(document).off('click', this.wrapSelector('.vp-inner-popup-apply-del-cond'));
-                    $(document).on('click', this.wrapSelector('.vp-inner-popup-apply-del-cond'), function() {
-                        // hide last operator
-                        $(this).closest('.vp-inner-popup-apply-cond-box').find('.vp-inner-popup-apply-oper-connect:last').hide();
-                        // delete apply cond
-                        $(this).parent().remove();
-                    });
-
-                    $(document).off('click', this.wrapSelector('.vp-inner-popup-apply-del-case'));
-                    $(document).on('click', this.wrapSelector('.vp-inner-popup-apply-del-case'), function() {
-                        // delete apply case
-                        $(this).parent().remove();
-                    });
                 }
 
                 // Replace page
@@ -872,6 +826,11 @@ define([
                         var tab = $(this).val();
                         $(that.wrapSelector('.vp-inner-popup-tab')).hide();
                         $(that.wrapSelector('.vp-inner-popup-tab.' + tab)).show();
+
+                        if (tab === 'apply') {
+                            let colName = $(that.wrapSelector('.vp-inner-popup-apply-column option:selected')).text();
+                            $(that.wrapSelector('.vp-inner-popup-apply-target-name')).text(colName);
+                        }
                     });
                 }
 
@@ -961,6 +920,93 @@ define([
                         $(useTextTag).prop('disabled', false);
                     }
                 });
+
+                // apply page
+                // 4. apply
+                $(this.wrapSelector('.vp-inner-popup-apply-column')).on('change', function() {
+                    // TODO: change apply-condition (save value)
+                    let colLabel = $(that.wrapSelector('.vp-inner-popup-apply-column option:selected')).text();
+                    $(that.wrapSelector('.vp-inner-popup-apply-target-name')).text(colLabel);
+
+                    var varName = that.state.tempObj;
+                    let colName = $(this).val();
+                    var colDtype = $(this).find('option:selected').attr('data-dtype');
+                    var operTag = $(that.wrapSelector('.vp-inner-popup-apply-oper-list'));
+                    var condTag = $(that.wrapSelector('.vp-inner-popup-apply-condition'));
+                    // get result and load column list
+                    vpKernel.getColumnCategory(varName, colName).then(function (resultObj) {
+                        let { result } = resultObj;
+                        try {
+                            var category = JSON.parse(result);
+                            if (category && category.length > 0 && colDtype == 'object') {
+                                // if it's categorical column and its dtype is object, check 'Text' as default
+                                $(that.wrapSelector('.vp-inner-popup-apply-cond-usetext')).prop('checked', true);
+                            } else {
+                                $(that.wrapSelector('.vp-inner-popup-apply-cond-usetext')).prop('checked', false);
+                            }
+                            $(operTag).replaceWith(function () {
+                                return that.templateForConditionOperator(colDtype, 'vp-inner-popup-apply-oper-list');
+                            });
+                            $(condTag).replaceWith(function () {
+                                return that.templateForConditionCondInput(category, colDtype, 'vp-inner-popup-apply-condition');
+                            });
+                        } catch {
+                            $(that.wrapSelector('.vp-inner-popup-apply-cond-usetext')).prop('checked', false);
+                            $(operTag).replaceWith(function () {
+                                return that.templateForConditionOperator(colDtype, 'vp-inner-popup-apply-oper-list');
+                            });
+                            $(condTag).replaceWith(function () {
+                                return that.templateForConditionCondInput([], colDtype, 'vp-inner-popup-apply-condition');
+                            });
+                        }
+                    });
+                });
+
+                $(this.wrapSelector('.vp-inner-popup-toggle-else')).on('click', function() {
+                    // toggle else on/off
+                    let elseOn = $(this).attr('data-else'); // on / off
+                    if (elseOn === 'off') {
+                        // off -> on
+                        $(this).attr('data-else', 'on');
+                        $(this).text('Else off');
+                        $(that.wrapSelector('.vp-inner-popup-apply-else-value')).prop('disabled', false);
+                        $(that.wrapSelector('.vp-inner-popup-apply-else-usetext')).prop('disabled', false);
+                    } else {
+                        // on -> off
+                        $(this).attr('data-else', 'off');
+                        $(this).text('Else on');
+                        $(that.wrapSelector('.vp-inner-popup-apply-else-value')).prop('disabled', true);
+                        $(that.wrapSelector('.vp-inner-popup-apply-else-usetext')).prop('disabled', true);
+                    }
+                });
+
+                $(this.wrapSelector('.vp-inner-popup-add-case')).on('click', function() {
+                    // add case
+                    $(this).parent().find('.vp-inner-popup-apply-case-box').append($(that.templateForApplyCase()));
+                });
+
+                $(document).off('click', this.wrapSelector('.vp-inner-popup-apply-add-cond'));
+                $(document).on('click', this.wrapSelector('.vp-inner-popup-apply-add-cond'), function() {
+                    // add condition
+                    $(this).parent().find('.vp-inner-popup-apply-cond-box').append($(that.templateForApplyCondition()));
+                    // show operator except last operator
+                    $(this).parent().find('.vp-inner-popup-apply-oper-connect:not(:last)').show();
+                });
+
+                $(document).off('click', this.wrapSelector('.vp-inner-popup-apply-del-cond'));
+                $(document).on('click', this.wrapSelector('.vp-inner-popup-apply-del-cond'), function() {
+                    let condBox = $(this).closest('.vp-inner-popup-apply-cond-box');
+                    // delete apply cond
+                    $(this).parent().remove();
+                    // hide last operator
+                    $(condBox).find('.vp-inner-popup-apply-oper-connect:last').hide();
+                });
+
+                $(document).off('click', this.wrapSelector('.vp-inner-popup-apply-del-case'));
+                $(document).on('click', this.wrapSelector('.vp-inner-popup-apply-del-case'), function() {
+                    // delete apply case
+                    $(this).parent().remove();
+                });
             } else if (menuType === FRAME_EDIT_TYPE.DISCRETIZE) {
                 // change bins
                 $(this.wrapSelector('.vp-inner-popup-bins')).on('change', function() {
@@ -1469,8 +1515,6 @@ define([
             content.appendLine('<label>Target column</label>');
             content.appendLine('<div class="vp-flex-gap5">');
             content.appendLine(this.renderColumnList(this.state.columnList));
-            // else on/off
-            content.appendFormatLine('<button class="vp-button {0}" data-else="off">Else On</button>', 'vp-inner-popup-toggle-else');
             content.appendLine('</div>');
             content.appendLine('</div>');
             // content.appendFormatLine('<textarea type="text" id="{0}" class="{1}" placeholder="{2}">lambda x: x</textarea>'
@@ -1483,7 +1527,8 @@ define([
             content.appendFormatLine('<button class="vp-button {0}">+ Case</button>', 'vp-inner-popup-add-case');
             content.appendLine('</div>'); 
             content.appendLine('<div class="vp-grid-col-120">');
-            content.appendLine('<label>Else value</label>');
+            // else on/off
+            content.appendFormatLine('<button class="vp-button {0}" data-else="off">Else On</button>', 'vp-inner-popup-toggle-else');
             content.appendLine('<div>');
             content.appendFormatLine('<input type="text" class="{0}" disabled/>', 'vp-inner-popup-apply-else-value');
             content.appendFormatLine('<label><input type="checkbox" class="{0}" checked disabled/><span>{1}</span></label>', 'vp-inner-popup-apply-else-usetext','Text');
@@ -1529,11 +1574,13 @@ define([
         }
 
         templateForApplyCondition() {
+            let colLabel = $(this.wrapSelector('.vp-inner-popup-apply-column option:selected'))?.text();
             return `<div class="vp-inner-popup-apply-cond-item wp100 mt5">
                 <div class="vp-icon-btn vp-icon-close-small vp-inner-popup-apply-del-cond" style="float:right;"></div>
+                <label class="vp-inner-popup-apply-target-name">${colLabel}</label>
                 ${this.templateForConditionOperator('', 'vp-inner-popup-apply-oper-list')}
                 <input class="vp-input m vp-inner-popup-apply-condition" type="text" placeholder="Value"/>
-                <label>
+                <label class="w60">
                     <input type="checkbox" class="vp-inner-popup-apply-cond-usetext" title="Uncheck it if you want to use variable or numeric values.">
                     <span>Text</span>
                 </label>
@@ -1558,9 +1605,13 @@ define([
         renderColumnList = function(columnList) {
             var selectTag = new com_String();
             selectTag.appendFormatLine('<select class="{0}">', 'vp-inner-popup-apply-column');
+            // columnList && columnList.forEach(col => {
+            //     selectTag.appendFormatLine('<option value="{0}">{1}</option>', col.code, col.label);
+            // }); 
             columnList && columnList.forEach(col => {
-                selectTag.appendFormatLine('<option value="{0}">{1}</option>', col.code, col.label);
-            }); 
+                selectTag.appendFormatLine('<option data-code="{0}" data-dtype="{1}" value="{2}">{3}</option>',
+                    col.code, col.type, col.code, col.label);
+            });
             selectTag.appendLine('</select>');
             return selectTag.toString();
         }
@@ -1826,6 +1877,7 @@ define([
             content.appendFormatLine('<td><select class="{0}">', 'vp-inner-popup-replacetype');
             content.appendFormatLine('<option value="{0}">{1}</option>', 'replace', 'Replace');
             content.appendFormatLine('<option value="{0}">{1}</option>', 'condition', 'Condition');
+            content.appendFormatLine('<option value="{0}">{1}</option>', 'apply', 'Apply');
             content.appendLine('</select></td></tr>');
             content.appendLine('</table>');
             content.appendLine('</div>'); // end of vp-inner-popup-header
@@ -1858,20 +1910,41 @@ define([
             content.appendLine('</div>');
             // condition table
             content.appendLine('<table class="vp-inner-popup-condition-tbl vp-tbl-gap5 wp100 mt5"><colgroup><col width="110px"><col width="*"></colgroup>');
-            // content.appendLine('<tr><td><label>Condition</label></td>');
-            // content.appendLine('<td><div class="vp-fr-subset-box">');
-            // content.appendLine('<textarea class="vp-input vp-inner-popup-subset"></textarea>');
-            // content.appendLine('</div></td>');
-            // content.appendLine('</tr>');
-
-            // content.appendLine('<tr><th><label>Variable</label></th>');
-            // content.appendFormatLine('<td><input type="text" class="{0}"/>', 'vp-inner-popup-input3');
-            // content.appendFormatLine('<label><input type="checkbox" class="{0}"/><span>{1}</span></label>', 'vp-inner-popup-istext3','Text');
             // content.appendLine('</td></tr>');
             content.appendLine(this.renderReplaceCondition());
             content.appendFormatLine('<tr><td colspan="4"><button type="button" class="vp-button {0}">{1}</button></td></tr>',
                 'vp-inner-popup-add-cond', '+ Condition');
             content.appendLine('</table>');
+            content.appendLine('</div>'); // end of vp-inner-popup-tab condition
+
+            // replace page - 3. apply
+            content.appendFormatLine('<div class="{0} {1}" style="display: none;">', 'vp-inner-popup-tab', 'apply');
+            content.appendLine('<div class="vp-grid-box">');
+            content.appendLine('<div class="vp-grid-col-120">');
+            content.appendLine('<label>Target column</label>');
+            content.appendLine('<div class="vp-flex-gap5">');
+            content.appendLine(this.renderColumnList(this.state.columnList));
+            content.appendLine('</div>');
+            content.appendLine('</div>');
+            // content.appendFormatLine('<textarea type="text" id="{0}" class="{1}" placeholder="{2}">lambda x: x</textarea>'
+            //                         , 'vp_popupAddApply', 'vp-input vp-inner-popup-apply-lambda', 'Type code manually');
+            // render condition
+            content.appendLine('<div class="vp-grid-box vp-scrollbar" style="max-height: 220px;">');
+            content.appendFormatLine('<div class="{0}">', 'vp-inner-popup-apply-case-box');
+            content.appendLine(this.templateForApplyCase());
+            content.appendLine('</div>');
+            content.appendFormatLine('<button class="vp-button {0}">+ Case</button>', 'vp-inner-popup-add-case');
+            content.appendLine('</div>'); 
+            content.appendLine('<div class="vp-grid-col-120">');
+            // else on/off
+            content.appendFormatLine('<button class="vp-button {0}" data-else="off">Else On</button>', 'vp-inner-popup-toggle-else');
+            content.appendLine('<div>');
+            content.appendFormatLine('<input type="text" class="{0}" disabled/>', 'vp-inner-popup-apply-else-value');
+            content.appendFormatLine('<label><input type="checkbox" class="{0}" checked disabled/><span>{1}</span></label>', 'vp-inner-popup-apply-else-usetext','Text');
+            content.appendLine('</div>');
+            content.appendLine('</div>'); // end of else value line
+            content.appendLine('</div>');
+            content.appendLine('</div>'); // end of vp-inner-popup-tab apply
 
             content.appendLine('</div>'); // end of vp-inner-popup-addpage
 
@@ -1960,9 +2033,9 @@ define([
             return content.toString();
         }
 
-        templateForConditionCondInput(category, dtype='object') {
+        templateForConditionCondInput(category, dtype='object', className='vp-inner-popup-condition') {
             var vpCondSuggest = new SuggestInput();
-            vpCondSuggest.addClass('vp-input m vp-inner-popup-condition');
+            vpCondSuggest.addClass('vp-input m ' + className);
 
             if (category && category.length > 0) {
                 vpCondSuggest.setPlaceholder((dtype=='object'?'Categorical':dtype) + " dtype");
@@ -2145,7 +2218,7 @@ define([
             switch (parseInt(type)) {
                 case FRAME_EDIT_TYPE.ADD_COL:
                     title = 'Add column';
-                    size = { width: 450, height: 480 };
+                    size = { width: 550, height: 480 };
                     content = this.renderAddPage('column');
                     this.renderAddValueBox(0);
 
@@ -2296,6 +2369,7 @@ define([
                 case FRAME_EDIT_TYPE.REPLACE:
                     title = 'Replace';
                     content = this.renderReplacePage();
+                    size = { width: 550, height: 480 };
 
                     // // bind codemirror
                     // this.subsetCm = this.initCodemirror({ 
@@ -2622,6 +2696,51 @@ define([
                         }
                         content['value'] = $(this.wrapSelector('.vp-inner-popup-condvalue')).val();
                         content['valueastext'] = $(this.wrapSelector('.vp-inner-popup-condvalueastext')).prop('checked');
+                    } else if (tab == 'apply') {
+                        content['target'] = $(this.wrapSelector('.vp-inner-popup-apply-column')).val();
+                        let caseList = [];
+                        $(this.wrapSelector('.vp-inner-popup-apply-case-item')).each((idx, caseTag) => {
+                            let condList = [];
+                            let replaceValue = $(caseTag).find('.vp-inner-popup-apply-case-val').val();
+                            let replaceValText = $(caseTag).find('.vp-inner-popup-apply-case-usetext').prop('checked');
+
+                            let operTag = $(caseTag).find('.vp-inner-popup-apply-oper-list');
+                            let condTag = $(caseTag).find('.vp-inner-popup-apply-condition');
+                            let condTextTag = $(caseTag).find('.vp-inner-popup-apply-cond-usetext');
+                            let operConnTag = $(caseTag).find('.vp-inner-popup-apply-oper-connect');
+                            for (let i=0; i<operTag.length; i++) {
+                                var oper = $(operTag[i]).val();
+                                var cond = $(condTag[i]).val();
+                                var condText = $(condTextTag[i]).prop('checked');
+                                var operConn = $(operConnTag[i]).val();
+                                var condObj = {};
+                                if (col !== '' && oper !== '' && cond !== '') {
+                                    condObj = {
+                                        oper: oper,
+                                        cond: com_util.convertToStr(cond, condText)
+                                    };
+                                    if (i < (operTag.length - 1)) {
+                                        condObj['connector'] = operConn;
+                                    }
+                                    condList.push(condObj);
+                                }
+                            };
+                            if (replaceValue !== '') {
+                                caseList.push({
+                                    value: com_util.convertToStr(replaceValue, replaceValText),
+                                    condList: condList
+                                });
+                            }
+                        });
+                        content['caseList'] = caseList;
+                        content['else'] = 'np.nan';
+                        if ($(this.wrapSelector('.vp-inner-popup-toggle-else')).attr('data-else') === 'on') {
+                            let elseValue = $(this.wrapSelector('.vp-inner-popup-apply-else-value')).val();
+                            let elseastext = $(this.wrapSelector('.vp-inner-popup-apply-else-usetext')).prop('checked');
+                            if (elseValue !== '') {
+                                content['else'] = com_util.convertToStr(elseValue, elseastext);
+                            }
+                        }
                     }
                     break;
                 case FRAME_EDIT_TYPE.RENAME:
@@ -3137,6 +3256,25 @@ define([
                         });
                         var value = com_util.convertToStr(content.value, content.valueastext);
                         code.appendFormat(", {0}] = {1}", content.name, value);
+                    } else if (tab == 'apply') {
+                        // code.appendFormat("{0}[{1}] = {2}[{3}].apply({4})", tempObj, content.name, tempObj, content.column, content.apply);
+                        let lambdaCode = 'lambda x: ';
+                        content['caseList'].forEach(obj => {
+                            // value if (cond list) else
+                            let caseCode = obj.value + ' ';
+                            let condCode = '';
+                            obj.condList.forEach((condObj, idx) => {
+                                let { oper, cond, connector } = condObj;
+                                condCode += `(x ${oper} ${cond})`;
+                                if (connector !== undefined) {
+                                    condCode += ` ${connector} `;
+                                }
+                            });
+                            caseCode += 'if ' + condCode + ' else ';
+                            lambdaCode += caseCode;
+                        });
+                        lambdaCode += content['else'];
+                        code.appendFormat("{0}[{1}] = {2}[{3}].apply({4})", tempObj, content.name, tempObj, content.target, lambdaCode);
                     }
                     break;
                 case FRAME_EDIT_TYPE.AS_TYPE:
diff --git a/visualpython/js/m_apps/Snippets.js b/visualpython/js/m_apps/Snippets.js
index a42cf5a6..3c031163 100644
--- a/visualpython/js/m_apps/Snippets.js
+++ b/visualpython/js/m_apps/Snippets.js
@@ -666,21 +666,23 @@ define([
             // load udf list to table 'vp_udfList'
             let loadingSpinner = new LoadingSpinner($(this.wrapSelector('.vp-sn-table')));
             vpConfig.getData().then(function(udfObj) {
-                vpLog.display(VP_LOG_TYPE.DEVELOP, udfObj);
+                vpLog.display(VP_LOG_TYPE.DEVELOP, 'Snippets get data', udfObj);
                 var snippets = new com_String();
-                Object.keys(udfObj).forEach(key => {
-                    let obj = udfObj[key];
-                    if (obj.code != null && obj.code != undefined) {
-    
-                        var hasImported = false;
-                        if (that.importedList.includes(key)) {
-                            // set new label
-                            hasImported = true;
+                if (udfObj !== undefined) {
+                    Object.keys(udfObj).forEach(key => {
+                        let obj = udfObj[key];
+                        if (obj.code != null && obj.code != undefined) {
+        
+                            var hasImported = false;
+                            if (that.importedList.includes(key)) {
+                                // set new label
+                                hasImported = true;
+                            }
+                            var item = that.renderSnippetItem(key, obj.code, obj.timestamp, hasImported);
+                            snippets.append(item);
                         }
-                        var item = that.renderSnippetItem(key, obj.code, obj.timestamp, hasImported);
-                        snippets.append(item);
-                    }
-                });
+                    });
+                }
                 $(that.wrapSelector('.vp-sn-table')).html(snippets.toString());
 
                 // bind snippet item
@@ -693,7 +695,8 @@ define([
                     that.bindCodeMirror(title, tag);
                 });
             }).catch(function(err) {
-                com_util.renderAlertModal(err);
+                // com_util.renderAlertModal(err);
+                vpLog.display(VP_LOG_TYPE.ERROR, err);
             }).finally(function() {
                 loadingSpinner.remove();
             });
diff --git a/visualpython/js/m_stats/Anova.js b/visualpython/js/m_stats/Anova.js
index 191ed0f7..66ad16ce 100644
--- a/visualpython/js/m_stats/Anova.js
+++ b/visualpython/js/m_stats/Anova.js
@@ -32,6 +32,7 @@ define([
             /** Write codes executed before rendering */
             this.config.sizeLevel = 2;
             this.config.checkModules = ['pd', 'np', 'scipy.stats', 'vp_confidence_interval', 'vp_sem'];
+            this.config.docs = 'https://docs.scipy.org/doc/scipy/reference/';
 
             this.state = {
                 testType: 'one-way',
diff --git a/visualpython/js/m_stats/CorrAnalysis.js b/visualpython/js/m_stats/CorrAnalysis.js
index 1620b82f..92251434 100644
--- a/visualpython/js/m_stats/CorrAnalysis.js
+++ b/visualpython/js/m_stats/CorrAnalysis.js
@@ -32,6 +32,7 @@ define([
             /** Write codes executed before rendering */
             this.config.sizeLevel = 2;
             this.config.checkModules = ['pd'];
+            this.config.docs = 'https://docs.scipy.org/doc/scipy/reference/';
 
             this.state = {
                 data: '',
diff --git a/visualpython/js/m_stats/EqualVarTest.js b/visualpython/js/m_stats/EqualVarTest.js
index 9a01cda9..3f6569e1 100644
--- a/visualpython/js/m_stats/EqualVarTest.js
+++ b/visualpython/js/m_stats/EqualVarTest.js
@@ -33,6 +33,7 @@ define([
             /** Write codes executed before rendering */
             this.config.sizeLevel = 2;
             this.config.checkModules = ['pd'];
+            this.config.docs = 'https://docs.scipy.org/doc/scipy/reference/';
 
             this.state = {
                 testType: 'bartlett',
diff --git a/visualpython/js/m_stats/FactorAnalysis.js b/visualpython/js/m_stats/FactorAnalysis.js
index 45b26abf..6d5ca900 100644
--- a/visualpython/js/m_stats/FactorAnalysis.js
+++ b/visualpython/js/m_stats/FactorAnalysis.js
@@ -33,6 +33,7 @@ define([
             this.config.sizeLevel = 2;
             this.config.checkModules = ['pd'];
             this.config.installButton = true;
+            this.config.docs = 'https://factor-analyzer.readthedocs.io/en/latest/factor_analyzer.html';
 
             this.state = {
                 data: '',
diff --git a/visualpython/js/m_stats/LogisticRegression.js b/visualpython/js/m_stats/LogisticRegression.js
index f4f2b169..318f63f6 100644
--- a/visualpython/js/m_stats/LogisticRegression.js
+++ b/visualpython/js/m_stats/LogisticRegression.js
@@ -33,6 +33,7 @@ define([
             /** Write codes executed before rendering */
             this.config.sizeLevel = 2;
             this.config.checkModules = ['pd'];
+            this.config.docs = 'https://www.statsmodels.org/stable/api.html';
 
             this.state = {
                 data: '',
diff --git a/visualpython/js/m_stats/NormTest.js b/visualpython/js/m_stats/NormTest.js
index e754c22d..0669f6cf 100644
--- a/visualpython/js/m_stats/NormTest.js
+++ b/visualpython/js/m_stats/NormTest.js
@@ -32,6 +32,7 @@ define([
             /** Write codes executed before rendering */
             this.config.sizeLevel = 2;
             this.config.checkModules = ['pd'];
+            this.config.docs = 'https://docs.scipy.org/doc/scipy/reference/';
 
             this.state = {
                 testType: 'shapiro-wilk',
diff --git a/visualpython/js/m_stats/ProbDist.js b/visualpython/js/m_stats/ProbDist.js
index ff110664..387c59b1 100644
--- a/visualpython/js/m_stats/ProbDist.js
+++ b/visualpython/js/m_stats/ProbDist.js
@@ -33,6 +33,7 @@ define([
             /** Write codes executed before rendering */
             this.config.sizeLevel = 2;
             this.config.checkModules = ['pd', 'plt'];
+            this.config.docs = 'https://docs.scipy.org/doc/scipy/reference/';
 
             this.state = {
                 distType: 'normal',
diff --git a/visualpython/js/m_stats/Regression.js b/visualpython/js/m_stats/Regression.js
index 0a0896a0..68b2c49d 100644
--- a/visualpython/js/m_stats/Regression.js
+++ b/visualpython/js/m_stats/Regression.js
@@ -34,6 +34,7 @@ define([
             /** Write codes executed before rendering */
             this.config.sizeLevel = 2;
             this.config.checkModules = ['pd'];
+            this.config.docs = 'https://www.statsmodels.org/stable/api.html';
 
             this.state = {
                 testType: 'simple',
diff --git a/visualpython/js/m_stats/StudentstTest.js b/visualpython/js/m_stats/StudentstTest.js
index 2b57bb87..fc0c7341 100644
--- a/visualpython/js/m_stats/StudentstTest.js
+++ b/visualpython/js/m_stats/StudentstTest.js
@@ -33,6 +33,7 @@ define([
             /** Write codes executed before rendering */
             this.config.sizeLevel = 2;
             this.config.checkModules = ['pd'];
+            this.config.docs = 'https://docs.scipy.org/doc/scipy/reference/';
 
             this.state = {
                 testType: 'one-sample',
diff --git a/visualpython/js/m_visualize/Chart.js b/visualpython/js/m_visualize/Chart.js
index f94ce966..f5336bb9 100644
--- a/visualpython/js/m_visualize/Chart.js
+++ b/visualpython/js/m_visualize/Chart.js
@@ -35,6 +35,7 @@ define([
             this.config.dataview = false;
             this.config.sizeLevel = 2;
             this.config.checkModules = ['plt'];
+            this.config.docs = 'https://matplotlib.org/stable/';
             
             this.setDefaultVariables();
             this.state = {
diff --git a/visualpython/js/m_visualize/Plotly.js b/visualpython/js/m_visualize/Plotly.js
index e77db277..9136883f 100644
--- a/visualpython/js/m_visualize/Plotly.js
+++ b/visualpython/js/m_visualize/Plotly.js
@@ -34,6 +34,7 @@ define([
             this.config.importButton = true;
             this.config.dataview = false;
             this.config.checkModules = ['px'];
+            this.config.docs = 'https://plotly.com/python-api-reference/index.html';
 
             this.state = {
                 chartType: 'scatter',
diff --git a/visualpython/js/m_visualize/Seaborn.js b/visualpython/js/m_visualize/Seaborn.js
index 56958fed..b153c3b0 100644
--- a/visualpython/js/m_visualize/Seaborn.js
+++ b/visualpython/js/m_visualize/Seaborn.js
@@ -32,6 +32,7 @@ define([
             this.config.dataview = false;
             this.config.size = { width: 1064, height: 550 };
             this.config.checkModules = ['plt', 'sns'];
+            this.config.docs = 'https://seaborn.pydata.org/index.html';
 
             this.state = {
                 chartType: 'scatterplot',
diff --git a/visualpython/js/m_visualize/WordCloud.js b/visualpython/js/m_visualize/WordCloud.js
index b361ed65..005e306b 100644
--- a/visualpython/js/m_visualize/WordCloud.js
+++ b/visualpython/js/m_visualize/WordCloud.js
@@ -32,6 +32,7 @@ define([
             this.config.importButton = true;
             this.config.dataview = false;
             this.config.checkModules = ['Counter', 'plt', 'WordCloud'];
+            this.config.docs = 'https://amueller.github.io/word_cloud/references.html';
 
             this.state = {
                 data: '',


Test patch:
diff --git a/visualpython/js/m_stats/Chi2test.js b/visualpython/js/m_stats/Chi2test.js
index 4eaaf70a..03d3cab2 100644
--- a/visualpython/js/m_stats/Chi2test.js
+++ b/visualpython/js/m_stats/Chi2test.js
@@ -32,6 +32,7 @@ define([
             /** Write codes executed before rendering */
             this.config.sizeLevel = 2;
             this.config.checkModules = ['pd'];
+            this.config.docs = 'https://docs.scipy.org/doc/scipy/reference/';
 
             this.state = {
                 data: '',