Can't access/update class object properties after creation - javascript - javascript

The problem is I get "Uncaught ReferenceError: holder is not defined" when trying to access object properties after object creation.
I'm doing the To Do List project for the the Odin Project.
JSFIDDLE: https://jsfiddle.net/appredator/8tcg7yo9/312/
Essentially, I want to click the add button, gather the values from the input fields, and save the values to their correct properties in a corresponding object and then display them as well.
//HTML
<div class="grid-container">
<h2 class="projectName">
Project
</h2>
<h2 class="task">
Task
</h2>
<h2 class="dueDate">
Due Date
</h2>
<h2 class="status">
Status
</h2>
</div>
<!-- End Header Div -->
<!-- Begin To Do List Grid Rows -->
<div class="projects">
</div>
<!-- Button at bottom of page -->
<button id="addProject">
Add To Do
</button>
//JS
class Project {
constructor(name) {
this.name = name;
}
}
class ToDo {
constructor(projectName, task, dueDate, status) {
this.projectName = projectName;
this.task = task;
this.dueDate = dueDate;
this.status = status;
this.list =
[{
task: "",
status: "",
dueDate: "",
}];
}
}
// Add Project Button Logic
const addButton = document.querySelector("#addProject");
addButton.addEventListener('click', addProject);
function addProject(){
let counter = 0;
//Select bottom div for updates etc
var content = document.querySelector(".projects");
//Save projectNameField contents when enter is pressed on the ProjectName Field
const projectNameField = document.createElement("input");
projectNameField.classList.add('projectNameCell');
content.appendChild(projectNameField);
projectNameField.addEventListener('keydown', function onEvent(e) {
if (e.key === 'Enter' || e.key === 'Tab') {
//Get and save project name
/* var project = projectNameField.value; */
//Instantiate Project constructor with DOM value
let project = new Project(projectNameField.value);
console.log(e.key)
projectNameField.remove();
//Use constructor to display project name in text
const projectNameDisplay = document.createElement("h3");
projectNameDisplay.classList.add('projectNameCell');
projectNameDisplay.innerHTML = project.name;
content.appendChild(projectNameDisplay);
}
});
//Save titleField contents when enter is pressed on the ProjectName Field
const taskField = document.createElement("input");
taskField.classList.add('taskCell');
content.appendChild(taskField);
taskField.addEventListener('keydown', function onEvent(e) {
if (e.key === 'Enter' || e.key === 'Tab') {
//Get and save title
//Instantiate toDo constructor with DOM value
let holder = new ToDo(taskField.value);
holder.projectName = document.querySelector(".projectNameCell").innerHTML;
holder.task = taskField.value;
console.log(holder.task)
//Use constructor to display project name in text
const taskDisplay = document.createElement("h3");
taskDisplay.classList.add('titleCell');
taskDisplay.innerHTML = holder.task;
taskField.remove();
content.appendChild(taskDisplay);
}
});
const dueDateField = document.createElement("input");
dueDateField.classList.add('dueDateCell');
content.appendChild(dueDateField);
dueDateField.addEventListener('keydown', function onEvent(e) {
if (e.key === 'Enter' || e.key === 'Tab') {
//Instantiate toDo constructor with DOM value
holder.dueDate = dueDateField.value;
/* console.log(task.list.dueDate) */
dueDateField.remove();
//Use constructor to display project name in text
const dueDateDisplay = document.createElement("h3");
dueDateDisplay.classList.add('dueDateCell');
dueDateDisplay.innerHTML = holder.dueDate;
content.appendChild(dueDateDisplay);
}
});
const statusField = document.createElement("input");
statusField.classList.add('statusCell');
content.appendChild(statusField);
statusField.addEventListener('keydown', function onEvent(e) {
if (e.key === 'Enter' || e.key === 'Tab') {
//Instantiate toDo constructor with DOM value
task.status = statusField.value;
console.log(task.dueDate)
statusField.remove();
//Use constructor to display project name in text
const statusDisplay = document.createElement("h3");
statusDisplay.classList.add('statusCell');
statusDisplay.innerHTML = task.status;
content.appendChild(statusDisplay);
}
});
counter++;
}
**I have tried adding get / set methods to the ToDo class with no luck. I also tried factory functions...
This is a scoping problem right? The properties are local scoped to the holder object?... I cut my code down on this post, but the full code is at jsfiddle. The reference error happens after you hit tab or enter on the 3rd input field.... Please advise. I'm kind of puzzled why objects would be so hard to access and update after creation lol. I created the object so I could use it. I know it's something trivial about scope and when to use constructors/classes/factories, but I cant figure it out.**

Related

How to create dependent dropdowns when adding it dynamically?

How do I create a dynamic dependent dropdown list in Google AppsScript? What I want to achieve is, when I choose PH in order type, the product selection dropdown should have ['PH-Test Product 1', 'PH-Test Product 2', 'PH-Test Product 3'] options. And when I choose EC it should have ['EC-Test Product 1', 'EC-Test Product 2', 'EC-Test Product 3'].
Here's my code-
form.html
<head>
<base target="_top">
<?!= include('css_script'); ?>
</head>
<body>
<div class="container">
<div class = "row">
<h1>A Sample Form</h1>
</div>
<div id="productsection"></div>
<div class = "row">
<button id="addproduct">Add Product</button>
</div> <!-- end of row -->
</div>
<?!= include('js_script'); ?>
</body>
js_script.html
<script>
let counter = 0;
const orderTypeList = ["PH", "EC"];
const optionListPH = ["PH-Test Product 1", "PH-Test Product 2", "PH-Test Product 3"];
const optionListEC = ["EC-Test Product 1", "EC-Test Product 2", "EC-Test Product 3"];
document.getElementById("addproduct").addEventListener("click", addInputField);
function addInputField(){
counter++;
// creates a new div of class row
const newDivElem = createElementTemplate('div', `row${counter}`, 'row');
// creates a new select tag for order type dropdown
const newOrderTypeSelectElem = createElementTemplate('select', `ordertype${counter}`);
// function that populates the dropdown for products and is inserted to the above "ordertypeX" select tag
createOptionsElem(newOrderTypeSelectElem, orderTypeList);
// creates a new select tag for product dropdown
const newProductSelectElem = createElementTemplate('select', `product${counter}`);
// Code to switch options depending on ordertype
//------------------------- Does Not Work --------------------------
if(document.getElementById(`ordertype${counter}`).value === 'PH'){
const optionList = optionListPH;
}else{
const optionList = optionListEC;
}
//------------------------------------------------------------------
// generates the content of the dropdown for products and is inserted to the above "productX" select tag
createOptionsElem(newProductSelectElem, optionList);
newDivElem.appendChild(newOrderTypeSelectElem);
newDivElem.appendChild(newProductSelectElem);
// Finally, appends the newly created div tag to the productSection tag.
document.getElementById('productsection').appendChild(newDivElem);
}
function createOptionsElem(selectTag, optionsArr){
const newDefaultOptionTag = document.createElement('option');
newDefaultOptionTag.value = "";
// newDefaultOptionTag.select = false;
newDefaultOptionTag.textContent="Choose your option";
for(let i in optionsArr){
const newOptionTag = document.createElement('option');
newOptionTag.textContent = optionsArr[i];
newOptionTag.value = optionsArr[i];
// Inserts the option tag in select tag
selectTag.appendChild(newOptionTag);
}
}
// function to create a new element
function createElementTemplate(tagType, idVal, className){
const newElement = document.createElement(tagType);
if(idVal !== undefined)
newElement.id = idVal;
if(className !== undefined)
newElement.classList.add(className);
return newElement;
}
</script>
css_script.html
<style>
.row{
margin-top: 5px;
margin-bottom: 5px;
}
</style>
Code.gs
function doGet(e) {
Logger.log(e);
return HtmlService.createTemplateFromFile('form_basic').evaluate();
}
function include(fileName){
return HtmlService.createHtmlOutputFromFile(fileName).getContent();
}
Modification points:
In your script, when a button is clicked, 2 dropdown lists are created. By this, at the following script,
if(document.getElementById(`ordertype${counter}`).value === 'PH'){
const optionList = optionListPH;
}else{
const optionList = optionListEC;
}
in the case of your script, optionListPH is always used to the 1st dropdown list.
And, when you want to change the 2nd dropdown list by changing the 1st dropdown list, it is required to add more script for checking it.
When these points are reflected in your script, how about the following modification?
From:
// Code to switch options depending on ordertype
//------------------------- Does Not Work --------------------------
if(document.getElementById(`ordertype${counter}`).value === 'PH'){
const optionList = optionListPH;
}else{
const optionList = optionListEC;
}
//------------------------------------------------------------------
// generates the content of the dropdown for products and is inserted to the above "productX" select tag
createOptionsElem(newProductSelectElem, optionList);
newDivElem.appendChild(newOrderTypeSelectElem);
newDivElem.appendChild(newProductSelectElem);
// Finally, appends the newly created div tag to the productSection tag.
document.getElementById('productsection').appendChild(newDivElem);
To:
// Code to switch options depending on ordertype
//------------------------- Does Not Work --------------------------
const optionList = optionListPH; // Modified
//------------------------------------------------------------------
// generates the content of the dropdown for products and is inserted to the above "productX" select tag
createOptionsElem(newProductSelectElem, optionList);
newDivElem.appendChild(newOrderTypeSelectElem);
newDivElem.appendChild(newProductSelectElem);
// Finally, appends the newly created div tag to the productSection tag.
document.getElementById('productsection').appendChild(newDivElem);
// I added the below script.
newOrderTypeSelectElem.addEventListener("change", function() {
newProductSelectElem.innerHTML = "";
createOptionsElem(newProductSelectElem, this.value === 'PH' ? optionListPH : optionListEC);
});
When this modification is reflected in your script, when a button is clicked, 2 dropdown lists are created. And, when 1st dropdown list is changed, the 2nd dropdown list is refreshed with new values.
Note:
This modification is for your showing script. When you change your script, this script might not be able to be used. Please be careful about this.

How to create an onclick function that retrieves the object ID on the HTML and attach the ID onto the API to be fetched and used?

i'm a beginner developer in javascript trying to make a website to impress recruiters. I've succesfully retrieve information from my API and used it on my website which took me ages to figure out. Now the next problem is how do i make an onclick event that gets the ID i hardcoded onto the specific movie poster on my HTML, to be used in javascript to change the API address and get the data for that specific movie.
Notes: i have a modal onclick to host all the movie data on that modal.
My Javascript and HTML is below.
HTML
<div class="poster" onclick="toggleModal(); getimdbID()" id="tt4154796">
<div id="imdbID">tt4154796</div>
<img
src="https://m.media-amazon.com/images/M/MV5BMTc5MDE2ODcwNV5BMl5BanBnXkFtZTgwMzI2NzQ2NzM#._V1_SX300.jpg"
alt=""
class="poster__img"
/>
<p class="movie__title">Avengers: Endgame</p>
<p class="year">2019</p>
</div>
Javascript
async function getMovie() {
const movies = await fetch(`https://www.omdbapi.com/?i=tt4154796&apikey=4148fa0f`);
const movieData = await movies.json();
const { Title, Year, Runtime, Genre, Director, Actors, Plot, Awards, Poster, Metascore, imdbRating, imdbVotes, BoxOffice, } = movieData
document.getElementById('title').textContent = Title;
document.getElementById('year').textContent = Year;
document.getElementById('genre').textContent = Genre;
document.getElementById('runtime').textContent = Runtime;
document.getElementById('plot').textContent = Plot;
document.getElementById('director').textContent = Director;
document.getElementById('actors').textContent = Actors;
document.getElementById('awards').textContent = Awards;
document.getElementById('metascore').textContent = Metascore;
document.getElementById('boxoffice').textContent = BoxOffice;
document.getElementById('imdbRating').textContent = imdbRating;
document.getElementById('imdbVotes').textContent = imdbVotes;
document.getElementById('poster').src = Poster;
}
getMovie()
// ToggleModal
let isModalOpen = false;
function toggleModal() {
if(isModalOpen) {
isModalOpen = false;
return document.body.classList.remove("modal--open");
}
isModalOpen = true;
document.body.classList += " modal--open";
}
I've tried to create a function onclick to get the value of the ID onclick but that didn't work.
function getimdbID() {
let x = document.querySelector("#imdbID").value
console.log(x)
}
Im guessing you want your function to return the "tt4154796" thats contained within the div. If this is the case you'll want to use document.querySelector("#imdbID").innerText.
Since using .value is typically used to retrieve or change values entered into input fields.

Why when i am searching for something else is deleting the previous contents

Why when you are searching for something else is deleting the previous contents ?For example first you search for egg and show the contents but then when you search for beef the program deletes the egg and shows only beef.Code :
const searchBtn = document.getElementById('search-btn');
const mealList = document.getElementById('meal');
const mealDetailsContent = document.querySelector('.meal-details-content');
const recipeCloseBtn = document.getElementById('recipe-close-btn');
// event listeners
searchBtn.addEventListener('click', getMealList);
mealList.addEventListener('click', getMealRecipe);
recipeCloseBtn.addEventListener('click', () => {
mealDetailsContent.parentElement.classList.remove('showRecipe');
});
// get meal list that matches with the ingredients
function getMealList(){
let searchInputTxt = document.getElementById('search-input').value.trim();
fetch(`https://www.themealdb.com/api/json/v1/1/filter.php?i=${searchInputTxt}`)
.then(response => response.json())
.then(data => {
let html = "";
if(data.meals){
data.meals.forEach(meal => {
html += `
<div class = "meal-item" data-id = "${meal.idMeal}">
<div class = "meal-img">
<img src = "${meal.strMealThumb}" alt = "food">
</div>
<div class = "meal-name">
<h3>${meal.strMeal}</h3>
Get Recipe
</div>
</div>
`;
});
mealList.classList.remove('notFound');
} else{
html = "Sorry, we didn't find any meal!";
mealList.classList.add('notFound');
}
mealList.innerHTML = html;
});
}
It's because you are replacing the contents in the mealList element every time.
A simple workaround would be to retrieve the the innerHTML values before you update it.
Something like
let html = mealList.innerHTML;
rather than starting off empty every time you call the function should do the trick.

Creating an editable HTML table from 2D array

So I am trying to make a flashcards website, where users can add, edit, and delete flashcards. There are two cards - front and back. The user can already add words, but cannot edit or delete them. For the purposes of this question I will use an example array:
var flashcards = [["Uomo", "Man"],["Donna", "Woman"],["Ragazzo", "Boy"]]
But I would like a more user-friendly way to edit the flashcards, preferably using a table like this:
<table>
<tr>
<th>Front</th>
<th>Back</th>
</tr>
<tr>
<td><input type="text" name="flashcard" value="Uomo"> </td>
<td><input type="text" name="flashcard" value="Man"></td>
</tr>
<tr>
<td><input type="text" name="flashcard" value="Donna"></td>
<td><input type="text" name="flashcard" value="Woman"></td>
</tr>
<tr>
<td><input type="text" name="flashcard" value="Ragazzo"></td>
<td><input type="text" name="flashcard" value="Boy"></td>
</tr>
</table>
<button type="button">Add more</button>
<br>
<button type="button">Save changes</button>
So they can update their flashcards editing the input fields, or clicking "add more" and it creating a new row. Clicking "save changes" updates the array to the content of the table.
I don't mind it not being a HTML table per se, but something that is easy to edit for the user.
I just cannot figure out the best way to approach this. Any advice?
I already recommended VueJS - it really is a pretty good tool for this problem. Regardless, I have typed up a basic solution using vanilla JavaScript. For the editing part it uses the contenteditable HTML attribute which allows the end-user to double click an element and change it's textContent.
The html display is basic so you can change it however to fit your needs
<div id=style="width: 100%;">
<ul id="table" style="list-style-type: none; display: inline-block;">
</ul>
</div>
<script>
var flashcards = [["Uomo", "Man"],["Donna", "Woman"],["Ragazzo", "Boy"]];
var displayedCard = []; //Using a parallel array to keep track of which side is shown
for(var i = 0; i < flashcards.length; i++){
displayedCard.push(0);
}
function renderFlashcardTable(){ //This will do the initial rendering of the table
let ulTable = document.getElementById("table");
for(var i = 0; i < flashcards.length; i++){
let card = flashcards[i];
let indexOfSideShown = displayedCard[i];
let li = document.createElement("li");
let cardValueSpan = document.createElement("span");
cardValueSpan.innerHTML = card[indexOfSideShown]; //Get the value of the side of the card that is shown
cardValueSpan.setAttribute("contenteditable", "true");
cardValueSpan.oninput = function(e){ //This method gets called when the user de-selects the element they have been editing
let li = this.parentElement;
let sideIndex = parseInt(li.getAttribute("side-index"));
card[sideIndex] = this.textContent;
}
li.appendChild(cardValueSpan);
li.appendChild(getFlipSidesButton(li));
li.setAttribute("side-index", indexOfSideShown);
li.setAttribute("card-index", i);
ulTable.appendChild(li);
}
}
function getFlipSidesButton(listItem){//This is generated for each card and when clicked it "flips the switch"
let btn = document.createElement("button");
btn.innerHTML = "Flip card";
btn.onclick = function(e){
let card = flashcards[listItem.getAttribute("card-index")];
let index = parseInt(listItem.getAttribute("side-index"));
let nextSide = (index == 1) ? 0 : 1;
listItem.setAttribute("side-index", nextSide);
listItem.children[0].innerHTML = card[nextSide];
}
return btn;
}
renderFlashcardTable();
</script>
I've put together a working sample using pure native javascript with a data-driven approach. You can have a look and understand the way how data should be manipulated and worked with in large Js application.
The point here is to isolate the data and logic as much as possible.
Hope this help.
Codepen: https://codepen.io/DieByMacro/pen/rgQBPZ
(function() {
/**
* Default value for Front and Back
*/
const DEFAULT = {
front: '',
back: '',
}
/**
* Class Card: using for holding value of front and back.
* As well as having `update` method to handle new value
* from input itself.
*/
class Card {
constructor({front, back, id} = {}) {
this.front = front || DEFAULT.front;
this.back = back || DEFAULT.back;
this.id = id;
}
update = (side, value) => this[side] = value;
}
/**
* Table Class: handle rendering data and update new value
* according to the instance of Card.
*/
class Table {
constructor() {
this.init();
}
/** Render basic table and heading of table */
init = () => {
const table = document.querySelector('#table');
const thead = document.createElement('tr');
const theadContent = this.renderRow('th', thead, { front: 'Front', back: 'Back' })
const tbody = document.createElement('tbody');
table.appendChild(theadContent);
table.appendChild(tbody);
}
/** Handling add event from Clicking on Add button
* Note the `update: updateFnc` line, this means we will refer
* `.update()` method of Card instance with `updateFnc()`, this is
* used for update value Card instance itself.
*/
add = ({front, back, id, update: updateFnc }) => {
const tbody = document.querySelector('#table tbody');
const row = document.createElement('tr');
const rowWithInput = this.renderRow('td', row, {front, back, id, updateFnc});
tbody.appendChild(rowWithInput);
}
renderInput = (side, id, fnc) => {
const input = document.createElement('input');
input.setAttribute('type','text');
input.setAttribute('name',`${side}-value-${id}`)
input.addEventListener('change', e => this.onInputChangeHandler(e, side, fnc));
return input;
}
renderRow = ( tag, parent, { front, back, id, updateFnc }) => {
const frontColumn = document.createElement( tag );
const backColumn = document.createElement( tag );
/** Conditionally rendering based on `tag` type */
if ( tag === 'th') {
frontColumn.innerText = front;
backColumn.innerText = back;
}else {
/** Create two new inputs for each Card instance. Each handle
* each side (front, back)
*/
const inputFront = this.renderInput('front', id, updateFnc);
const inputBack = this.renderInput('back', id, updateFnc);
frontColumn.appendChild(inputFront);
backColumn.appendChild(inputBack);
}
parent.appendChild(frontColumn)
parent.appendChild(backColumn)
return parent;
}
/** Getting new value and run `.update()` method of Card, now referred as `fnc` */
onInputChangeHandler = (event, side, fnc) => {
fnc(side, event.target.value);
}
}
class App {
/**
* Holding cards data
* Notice this is an object, not an array
* Working with react for a while, I see most of the times data as an object works best when it comes to cRUD, this means we don't have to iterate through the array to find the specific element/item to do the work. This saves a lot of time
*/
cards = {};
constructor(){
this.domTable = new Table();
this.domAdd = document.querySelector('#btn-add');
this.domResult = document.querySelector('#btn-result');
this.domAdd.addEventListener('click', this.onClickAddHandler );
this.domResult.addEventListener('click', this.onClickResultHandler );
}
onClickAddHandler = () => {
const id = uuid();
const newCard = new Card({id});
this.cards[id] = newCard;
this.domTable.add(newCard)
}
onClickResultHandler = () => {
/**
* Using `for ... in ` with object. Or you can use 3rd party like lodash for iteration
*/
for (const id in this.cards) {
console.log({
front: this.cards[id].front,
back: this.cards[id].back,
id: this.cards[id].id
});
}
};
}
// Start the application
const app = new App();
})();
<script src="https://cdnjs.cloudflare.com/ajax/libs/node-uuid/1.4.8/uuid.min.js"></script>
<div id="table"></div>
<button id="btn-add">Add</button>
<button id="btn-result">Result</button>
i think you can use In-Place Editing System and there's a good tutorial i found
Create an In-Place Editing System

knockout.js - deferred databinding for modal?

I am using knockout.js to display a list of employees. I have a single hidden modal markup on the page. When the "details" button for a single employees is clicked, I want to data-bind that employee to the modal popup. I am using the ko.applyBindings(employee, element) but the problem is when the page loads, it is expecting the modal to start off as bound to something.
So I'm wondering, is there a trick/strategy to do a late/deferred databinding? I looked into virtual bindings but the documentation was not helpful enough.
Thanks!
I would like to propose a different way to work with modals in MVVVM. In MVVM, the ViewModel is data for the View, and the View is responsible for the UI. If we examine this proposal:
this.detailedEmployee = ko.observable({}),
var self = this;
this.showDetails = function(employee){
self.detailedEmployee(employee);
$("#dialog").dialog("show"); //or however your dialog works
}
I strongly agree with this.detailedEmployee = ko.observable({}), but I am in strong disagreement with this line: $("#dialog").dialog("show");. This code is placed in the ViewModel and shows the modal window, wherein fact it is View's responsibility, so we screw-up the MVVM approach. I would say this piece of code will solve your current task but it could cause lots of problems in future.
When closing the popup, you should set detailedEmployee to undefined to have your main ViewModel in a consistent state.
When closing the popup, you might want to have validation and the possibility to discard the close operation when you want to use another modal's component in the application
As for me, these points are very critical, so I would like to propose a different way. If we "forget" that you need to display data in popup, binding with could solve your issue.
this.detailedEmployee = ko.observable(undefined);
var self = this;
this.showDetails = function(employee){
self.detailedEmployee(employee);
}
<div data-bind="with: detailedEmployee">
Data to show
</div>
As you can see, your ViewModel don't know anything about how data should be shown. It knows only about data that should be shown. The with binding will display content only when detailedEmployee is defined. Next, we should find a binding similar to with but one that will display content in the popup. Let's give it the name modal. Its code is like this:
ko.bindingHandlers['modal'] = {
init: function(element) {
$(element).modal('init');
return ko.bindingHandlers['with'].init.apply(this, arguments);
},
update: function(element, valueAccessor) {
var value = ko.utils.unwrapObservable(valueAccessor());
var returnValue = ko.bindingHandlers['with'].update.apply(this, arguments);
if (value) {
$(element).modal('show');
} else {
$(element).modal('hide');
}
return returnValue;
}
};
As you can see, it uses the with plugin internally, and shows or hide a popup depending on value passed to binding. If it is defined - 'show'. If not - 'hide'. Its usage will be the as with:
<div data-bind="modal: detailedEmployee">
Data to show
</div>
The only thing you need to do is to use your favorite modals plugin. I prepared an example with the Twitter Bootstrap popup component: http://jsfiddle.net/euvNr/embedded/result/
In this example, custom binding is a bit more powerful; you could subscribe the onBeforeClose event and cancel this event if needed. Hope this helps.
The JSFiddle linked to in the answer provided by #Romanych didn't seem to work anymore.
So, I built my own example (based upon his original fiddle) with full CRUD support and basic validation using Bootstrap 3 and the Bootstrap Modal library: https://jsfiddle.net/BitWiseGuy/4u5egybp/
Custom Binding Handlers
ko.bindingHandlers['modal'] = {
init: function(element, valueAccessor, allBindingsAccessor) {
var allBindings = allBindingsAccessor();
var $element = $(element);
$element.addClass('hide modal');
if (allBindings.modalOptions && allBindings.modalOptions.beforeClose) {
$element.on('hide', function() {
var value = ko.utils.unwrapObservable(valueAccessor());
return allBindings.modalOptions.beforeClose(value);
});
}
},
update: function(element, valueAccessor) {
var value = ko.utils.unwrapObservable(valueAccessor());
if (value) {
$(element).removeClass('hide').modal('show');
} else {
$(element).modal('hide');
}
}
};
Example Usage
The View
<div data-bind="modal: UserBeingEdited" class="fade" role="dialog" tabindex="-1">
<form data-bind="submit: $root.SaveUser">
<div class="modal-header">
<a class="close" data-dismiss="modal">×</a>
<h3>User Details</h3>
</div>
<div class="modal-body">
<div class="form-group">
<label for="NameInput">Name</label>
<input type="text" class="form-control" id="NameInput" placeholder="User's name"
data-bind="value: UserBeingEdited() && UserBeingEdited().Name, valueUpdate: 'afterkeydown'">
</div>
<div class="form-group">
<label for="AgeInput">Age</label>
<input type="text" class="form-control" id="AgeInput" placeholder="User's age"
data-bind="value: UserBeingEdited() && UserBeingEdited().Age, valueUpdate: 'afterkeydown'">
</div>
<!-- ko if: ValidationErrors() && ValidationErrors().length > 0 -->
<div class="alert alert-danger" style="margin: 20px 0 0">
Please correct the following errors:
<ul data-bind="foreach: { data: ValidationErrors, as: 'errorMessage' }">
<li data-bind="text: errorMessage"></li>
</ul>
</div>
<!-- /ko -->
</div>
<div class="modal-footer">
<button type="button" data-dismiss="modal" class="btn btn-default">Cancel</button>
<button type="submit" class="btn btn-primary">Save Changes</button>
</div>
</form>
</div>
The ViewModel
/* ViewModel for the individual records in our collection. */
var User = function(name, age) {
var self = this;
self.Name = ko.observable(ko.utils.unwrapObservable(name));
self.Age = ko.observable(ko.utils.unwrapObservable(age));
}
/* The page's main ViewModel. */
var ViewModel = function() {
var self = this;
self.Users = ko.observableArray();
self.ValidationErrors = ko.observableArray([]);
// Logic to ensure that user being edited is in a valid state
self.ValidateUser = function(user) {
if (!user) {
return false;
}
var currentUser = ko.utils.unwrapObservable(user);
var currentName = ko.utils.unwrapObservable(currentUser.Name);
var currentAge = ko.utils.unwrapObservable(currentUser.Age);
self.ValidationErrors.removeAll(); // Clear out any previous errors
if (!currentName)
self.ValidationErrors.push("The user's name is required.");
if (!currentAge) {
self.ValidationErrors.push("Please enter the user's age.");
} else { // Just some arbitrary checks here...
if (Number(currentAge) == currentAge && currentAge % 1 === 0) { // is a whole number
if (currentAge < 2) {
self.ValidationErrors.push("The user's age must be 2 or greater.");
} else if (currentAge > 99) {
self.ValidationErrors.push("The user's age must be 99 or less.");
}
} else {
self.ValidationErrors.push("Please enter a valid whole number for the user's age.");
}
}
return self.ValidationErrors().length <= 0;
};
// The instance of the user currently being edited.
self.UserBeingEdited = ko.observable();
// Used to keep a reference back to the original user record being edited
self.OriginalUserInstance = ko.observable();
self.AddNewUser = function() {
// Load up a new user instance to be edited
self.UserBeingEdited(new User());
self.OriginalUserInstance(undefined);
};
self.EditUser = function(user) {
// Keep a copy of the original instance so we don't modify it's values in the editor
self.OriginalUserInstance(user);
// Copy the user data into a new instance for editing
self.UserBeingEdited(new User(user.Name, user.Age));
};
// Save the changes back to the original instance in the collection.
self.SaveUser = function() {
var updatedUser = ko.utils.unwrapObservable(self.UserBeingEdited);
if (!self.ValidateUser(updatedUser)) {
// Don't allow users to save users that aren't valid
return false;
}
var userName = ko.utils.unwrapObservable(updatedUser.Name);
var userAge = ko.utils.unwrapObservable(updatedUser.Age);
if (self.OriginalUserInstance() === undefined) {
// Adding a new user
self.Users.push(new User(userName, userAge));
} else {
// Updating an existing user
self.OriginalUserInstance().Name(userName);
self.OriginalUserInstance().Age(userAge);
}
// Clear out any reference to a user being edited
self.UserBeingEdited(undefined);
self.OriginalUserInstance(undefined);
}
// Remove the selected user from the collection
self.DeleteUser = function(user) {
if (!user) {
return falase;
}
var userName = ko.utils.unwrapObservable(ko.utils.unwrapObservable(user).Name);
// We could use another modal here to display a prettier dialog, but for the
// sake of simplicity, we're just using the browser's built-in functionality.
if (confirm('Are you sure that you want to delete ' + userName + '?')) {
// Find the index of the current user and remove them from the array
var index = self.Users.indexOf(user);
if (index > -1) {
self.Users.splice(index, 1);
}
}
};
}
Initializing Knockout with the View and the ViewModel
var viewModel = new ViewModel();
// Populate the ViewModel with some dummy data
for (var i = 1; i <= 10; i++) {
var letter = String.fromCharCode(i + 64);
var userName = 'User ' + letter;
var userAge = i * 2;
viewModel.Users.push(new User(userName, userAge));
}
// Let Knockout do its magic!
ko.applyBindings(viewModel);
I would create another observable that wraps the employee.
this.detailedEmployee = ko.observable({}),
var self = this;
this.showDetails = function(employee){
self.detailedEmployee(employee);
$("#dialog").dialog("show"); //or however your dialog works
}
Attach the click to showDetails. Then you can just call applyBindings on page load.

Categories