determining which item of a list was clicked- javascript - javascript

I have a winJS list which is full of images. When a user clicks on an image, they are being sent to a second page. Is there any way of being able to determine which image the user clicked on page 1 (each image, when clicked, goes to the same page 2 but with a custom div). Here is my list declared and then I am push items to it:
var names_Array = [];
var names_List = new WinJS.Binding.List(names_Array);
var idPL;
names_List.push({ name: "man 1", image: "image/man1.png", ClientID: "1111" });
names_List.push({ name: "man 2 ", image: "image/man2.png", ClientID: "2222" });
idPL = document.getElementById("names_List");
idPL.addEventListener("iteminvoked", onclickItem);
function onclickItem(e) {
console.log("Item clicked");
}
A the minute I am populating the div based on which item was last pushed to the list, but I need this to be more flexible and be able to select the first item (even after a second one has been added)
EDIT: I AM NOW GETTING AN ERROR 'Unable to get property 'addEventListener' of undefined or null reference'. I THOUGHT I had defined it in my code above

To get the detail of which item is clicked in winjs listview we can use itemInvoke event of the Winjs listview.
<div id="itemsList" data-win-control="WinJS.Binding.Template" style="display:none;">
<div data-win-bind="className: type">
<img src="#" style="width: 100px; height: 100px;"
data-win-bind="alt: title; src: picture" />
<div>
<h4 data-win-bind="innerText: title" style="width:100px; height:20px;"></h4>
</div>
<div>
<img id="like"src="images/like.png" class="win-interactive" data-win-bind="alt:title; onclick: LikeClick;" />
</div>
</div>
</div>
<div id="UserListView" data-win-control="WinJS.UI.ListView" style="border-top: 5px solid #000; min-width:500px;"
data-win-options="{
selectionMode:'multi',
itemTemplate:select('#itemsList'),
layout:{
type:WinJS.UI.GridLayout}}"
>
</div>
then in the js
UserListView.addEventListener("iteminvoked", ItemInvoke);
function ItemInvoke(evt) {
var name=evt.detail.itemPromise._value.data.title;
var picturePath=evt.detail.itemPromise._value.data.picture;
}
Here in this listview we are only displaying the image and title of the user.

You can reformat your image urls to include:
A hash: image.html#man1.png
A query string: image.html?image=man1.png
And then use those to extract information from the URL using JavaScript for when the 2nd page is loaded.

Either assign an eventListener globally and check which was clicked and do something, or assign an eventListener per item and do whatever you require.
UPDATE: I am unable to demonstrate using WinJS, but this example should suffice.
HTML
<ul id="names_List"></ul>
Javascript
var names_Array = [],
idPL = document.getElementById("names_List");
names_Array.push({
name: "man 1",
image: "image/man1.png",
ClientID: "1111"
});
names_Array.push({
name: "man 2 ",
image: "image/man2.png",
ClientID: "2222"
});
names_Array.forEach(function (item) {
var li = document.createElement("li"),
img = document.createElement("img");
img.name = item.name;
img.src = item.img;
li.appendChild(img);
idPL.appendChild(li);
});
function onclickItem(e) {
if (e.target.tagName === "IMG") {
console.log("Item clicked", e.target.name);
}
}
idPL.addEventListener("click", onclickItem, false);
On jsfiddle

The best and easiest way you can do this is by declaring a namespace with the value of the image inside an eventlistener for selectionchanged, like this:
listview.addEventListener("selectionchanged", function (item) {
listview.selection.getItems().done(function (items) {
WinJS.Namespace.define("imageInformation", {
seletedImage: items[0].data.image,
});
}, false);
After the namespace is declared, you can access it on the other page:
var path = imageInformation.selectedImage;

Related

I have an issue with handlebars and a counter function in JavaScript

i'm trying create a counter function on object compilated with handlebars.js. I'm trying to make a project site for a moving company, and I'm trying to create a cost estimation system in it, depending on what things and how many they have to move in their new house
handlebars compilated divs
problem
So I created objects in js which contain things to move such as fridge, bed, table etc... their are compilated right into HTML with handlebars template.
I want to be able to increase and decrease the numbers of things separately, so I was able to create one function that does this, but the problem is that it works only on the first compilated object and all other objects are not affected with this code, i don't even know if it is possible to do such thing with handlebars template and vanilla js, I know I can do this for each objects individually but there will be way too much duplicated codes in HTML and JS files..
here is the handlebars template on HTML file:
<script src="https://cdn.jsdelivr.net/npm/handlebars#latest/dist/handlebars.js"></script>
<script id="templateHB" type="text/x-handlebars-template">
{{#each lesMeublesSalon}}
<div class="meuble"><img src="{{this.image}}">
<p>{{this.element}}</p>
<div class="plus-moin">
<button id="increase-{{this.index}}">+</button>
<p>{{this.quantity}}</p>
<button id="decrease-{{this.index}}">-</button>
</div>
</div>
{{/each}}
here is the js file code:
const source = document.getElementById('templateHB').innerHTML;
const template = Handlebars.compile(source);
const contextSalon = {
lesMeublesSalon: [
{
image: 'images/canape.png',
element: 'Canapé',
quantity: 0,
index: 0
},
{
image: 'images/canape.png',
element: 'Lit',
quantity: 0,
index: 0
}
]
};
let compiledHtmlSalon = template(contextSalon);
const injectionObjetSalon = document.getElementById('meuble-salon');
injectionObjetSalon.innerHTML = compiledHtmlSalon;
// here start the function
let quantity = contextSalon.lesMeublesSalon[0].quantity;
let addOneBtn = document.getElementById("increase-0");
let removeOneBtn = document.getElementById("decrease-0");
function updateQuantity(quantity) {
contextSalon.lesMeublesSalon[0].quantity = quantity;
compiledHtmlSalon = template(contextSalon);
injectionObjetSalon.innerHTML = compiledHtmlSalon;
addOneBtn = document.getElementById("increase-0");
removeOneBtn = document.getElementById("decrease-0");
addOneBtn.addEventListener("click", function() {
updateQuantity(quantity + 1);
});
removeOneBtn.addEventListener("click", function() {
updateQuantity(quantity - 1);
});
}
updateQuantity(0);
if the thing i'm trying to do is impossible with js and handlebars.js, what other tech can you suggest me? any js framework such as node.js and express.js?
I just created function to increase and decrease number based on ID of the template inside html file with handlebars, I was expecting it to work with all others compilated objects to work the same way.
It is possible that a rendering framework like React or Vue could be used for this, but they come with learning curves. The conditions as you have outlined them are fairly simple and so I think you can get by with Handlebars.
But we will need to make some modifications to your code.
The first issue I see is that you have index: 0 set on all objects in your lesMeublesSalon array. You could correct those to be sequential (0, 1...), but I think a better option would be to use Handlebars' built-in #index variable to set the index value for each item.
The next problem I would address is that you are trying to re-render the Handlebars template with each click of the increment/decrement button. The problem I see with this is that it is that you will have to re-attach your event listeners each time you re-render the HTML because the DOM will have new button elements that need to be listened to.
I think a better approach would be to render the initial HTML with Handlebars, but then to use JavaScript to directly update the DOM when your counts change.
Here is how I would implement this:
{{#each lesMeublesSalon}}
<div class="meuble"><img src="{{this.image}}">
<p>{{this.element}}</p>
<div class="plus-moin">
<button data-increase="{{#index}}">+</button>
<p data-quantity="{{#index}}">{{this.quantity}}</p>
<button data-decrease="{{#index}}">-</button>
</div>
</div>
{{/each}}
const source = document.getElementById('templateHB').innerHTML;
const template = Handlebars.compile(source);
const contextSalon = {
lesMeublesSalon: [
{
image: 'images/canape.png',
element: 'Canapé',
quantity: 0,
index: 0
},
{
image: 'images/canape.png',
element: 'Lit',
quantity: 0,
index: 0
}
]
};
// Initially render our HTML with Handlebars.
const compiledHtmlSalon = template(contextSalon);
const injectionObjetSalon = document.getElementById('meuble-salon');
injectionObjetSalon.innerHTML = compiledHtmlSalon;
// Note the use of data-* attributes in the template.
// This allows us to query for ALL increment/decrement buttons
// and to attach a listener to each.
// Getting them by ID allowed us to get only one increment
// and one decrement button.
const addOneBtns = document.querySelectorAll("[data-increase]");
const removeOneBtns = document.querySelectorAll("[data-decrease]");
const quantityDisplays = document.querySelectorAll("[data-quantity]");
// This is the function that will directly manipulate the displayed
// quantity value for each item.
// It relies on the indexes of the elements to match the indexes in
// our lesMeublesSalon so that the correct quantity will be set.
function renderQuantities () {
quantityDisplays.forEach((quantityDisplay, index) => {
const quantity = contextSalon.lesMeublesSalon[index].quantity;
quantityDisplay.textContent = String(quantity);
})
};
// We loop through EACH increment button and attach a click listener.
addOneBtns.forEach(addOneBtn => {
addOneBtn.addEventListener('click', function (event) {
// We get the index from the `[data-increase]` attribute.
const index = Number(event.target.dataset.increase);
// We use that index to increment the quantity on the
// corresponding item in our array.
contextSalon.lesMeublesSalon[index].quantity += 1;
// We re-render the quantities because we know there is a change.
renderQuantities();
});
});
// This is basically the same as above, except for decrementing.
removeOneBtns.forEach(removeOneBtn => {
removeOneBtn.addEventListener('click', function (event) {
const index = Number(event.target.dataset.decrease);
// We use Math.max() so the quantity can't become less than zero.
contextSalon.lesMeublesSalon[index].quantity = Math.max(contextSalon.lesMeublesSalon[index].quantity - 1, 0);
renderQuantities();
});
});
I have created a Codepen for reference.
I've tesed your code and it works well , thanks you! however I forgot to mention that I have 4 categories of objects , as you can see on the picture , I got (Salon, Chambre, Cuisine, Bain) , how can we make so that it works on all of these categories ?
So it works on the "Salon" categories but not in the others,
here you have all others object :
const contextSalon = {
lesMeublesSalon: [
{
image: 'images/canape.png',
element: 'Canapé',
quantity: 0,
index: 0
},
{
image: 'images/canape.png',
element: 'Lit',
quantity: 0,
index: 0
}
]
};
// Initially render our HTML with Handlebars.
const compiledHtmlSalon = template(contextSalon);
const injectionObjetSalon = document.getElementById('meuble-salon');
injectionObjetSalon.innerHTML = compiledHtmlSalon;
const contextChambre = {
lesMeublesChambre: [
{
image: 'images/bed.svg.png',
element: 'Lit double',
quantity: 0,
index: 0
}
]
}
const compiledHtmlChambre = template(contextChambre);
const injectionObjetChambre = document.getElementById('meuble-chambre');
injectionObjetChambre.innerHTML = compiledHtmlChambre;
const contextCuisine = {
lesMeublesCuisine: [
{
image: 'images/frigo.svg',
element: 'Frigo',
quantity: 0,
index: 0
}
]
}
const compiledHtmlCuisine = template(contextCuisine);
const injectionObjetCuisine = document.getElementById('meuble-cuisine');
injectionObjetCuisine.innerHTML = compiledHtmlCuisine;
const contextBain = {
lesMeublesBain: [
{
image: 'images/machine-a-laver.svg',
element: 'Machine à laver',
quantity: 0,
index: 0
}
]
}
const compiledHtmlBain = template(contextBain);
const injectionObjetBain = document.getElementById('meuble-bain');
injectionObjetBain.innerHTML = compiledHtmlBain;
{{#each lesMeublesSalon}}
<div class="meuble"><img src="{{this.image}}">
<p>{{this.element}}</p>
<div class="plus-moin">
<button data-increase="{{#index}}">+</button>
<p data-quantity="{{#index}}">{{this.quantity}}</p>
<button data-decrease="{{#index}}">-</button>
</div>
</div>
{{/each}}
{{#each lesMeublesChambre}}
<div class="meuble"><img src="{{this.image}}">
<p>{{this.element}}</p>
<div class="plus-moin">
<button>+</button>
<p>{{this.quantity}}</p>
<button>-</button>
</div>
</div>
{{/each}}
{{#each lesMeublesCuisine}}
<div class="meuble"><img src="{{this.image}}">
<p>{{this.element}}</p>
<div class="plus-moin">
<button>+</button>
<p>{{this.quantity}}</p>
<button>-</button>
</div>
</div>
{{/each}}
{{#each lesMeublesBain}}
<div class="meuble"><img src="{{this.image}}">
<p>{{this.element}}</p>
<div class="plus-moin">
<button>+</button>
<p>{{this.quantity}}</p>
<button>-</button>
</div>
</div>
{{/each}}

Iteratively print JavaScript dictionary of objects into HTML divs

I'd appreciate some help with iteratively generating the beneath div based on the amount of items in a Javascript dictionary.
<div class="container" style="padding-bottom: 10px;">
<div class="dropdown" style="padding: 10px;">
<a href="#">TOP 3 PS5 HEADSETS<i class="fa fa-chevron-down"></i>
</a>
<ul>
<div id="links">
<center>
<p>3: ↓ INSERT TITLE FOR STEELSERIES ↓</p>
</center>
<div class="product">
<img src="img/products/h-steelseries.png">
<a class="link" href="INSERT LINK HERE">Read More</a>
</div>
<center>
<p>3: ↓ INSERT TITLE FOR OTHER↓</p>
</center>
<div class="product">
<img src="img/products/h-other.png">
<a class="link" href="INSERT LINK HERE">Read More</a>
</div>
</div>
</ul>
</div>
</div>
Beneath is the read.js file that contains the items in which I wish to generate the div class "product" for.
I'd really apprecaite any help with this.
var prod_obj = {
"headphone_products" : {
"title": "Steelseries",
"IMAGE": "h-steelseries.png",
"HREF" : "steelseries.html"
},
"other_products" : {
"title": "Other product",
"IMAGE": "h-other.png",
"HREF" : "other.html"
}
};
I have looked at other answers and couldn't find an example of a dictionary of object that was used to automatically generate divs. I intend on using this to list items on a website and would like to append objects to the dictionary and them to automatically generate a new div for each object once the script is executed.
Thank you for your time.
You can simply loop over the object and create the desired nodes inside the loop.
Here's a simpler version of the same.
var prod_obj = {
"headphone_products": {
"title": "Steelseries",
},
"other_products": {
"title": "Other product",
}
};
for (let keys in prod_obj) {
const div = document.createElement("div");
div.innerText = prod_obj[keys].title
document.body.appendChild(div)
}
You can use for-in loops and template literals to achieve what you want to achieve here.
const prod_obj = {
"headphone_products": {
"title": "Steelseries",
"image": "h-steelseries.png",
"href": "steelseries.html"
},
"other_products": {
"title": "Other product",
"image": "h-other.png",
"href": "other.html"
}
};
const div = document.getElementById('insertHere');
for (let products_key in prod_obj) {
let {title, image, href} = prod_obj[products_key];
let html = `<p>Title: ${title}, Image: ${image}, href: ${href}</p>`;
div.insertAdjacentHTML('beforeend', html);
}
<div id="insertHere">
</div>
What you describe sounds like a suitable candidate for a template which, according to the documentation on MDN says:
The HTML Content Template () element is a mechanism for
holding HTML that is not to be rendered immediately when a page is
loaded but may be instantiated subsequently during runtime using
JavaScript.
The following uses a simple class to load a new instance of the designated template for each product found within the source data ( what you refer to as a dictionary ). Once the template has been loaded from the shadows you can manipulate the contents as you wish. If you change the design of the template you change the design of the final layout. In the original HTML there is no span element surrounding the individual products but the way I wrote the template loader( for a specific job ) clones the first child element entirely - so a span will not affect layout unless styled specifically to do so.
class TemplateLoader{
constructor( id ){
this.id=id;
return this.create();
};
gettemplate(){
return document.querySelector( 'template[ data-id="'+this.id+'" ]' ) || false
};
clone(){
let tmpl=this.gettemplate();
return tmpl ? tmpl.content.firstElementChild.cloneNode( true ) : false;
};
gettarget(){
return document.querySelector( 'div[ id="'+this.id+'" ]' ) || false;
};
create(){
let tmpl=this.clone();
if( tmpl ){
let target=this.gettarget();
target.appendChild( tmpl );
return tmpl;
}
return false;
};
};
var prod_obj = {
'headphone_products' : {
'title': 'steelseries',
'image': 'h-steelseries.png',
'href' : 'steelseries.html'
},
'other_products' : {
'title': 'other product',
'image': 'h-other.png',
'href' : 'other.html'
},
'banana':{
'title':'curvy & yellow',
'image':'b-a-nana.png',
'href':'banana.html'
}
};
let id='links';
Object.keys( prod_obj ).forEach( cat => {
let data=prod_obj[ cat ];
let oTmpl=new TemplateLoader( id );
oTmpl.querySelector('center > p').textContent=data.title;
oTmpl.querySelector('div.product > img').src=['img/products',data.image].join('/');
oTmpl.querySelector('div.product > a.link').href=data.href;
});
<!-- regular HTML -->
<div class='container'>
<div class='dropdown'>
<a href='#'>TOP 3 PS5 HEADSETS<i class='fa fa-chevron-down'></i></a>
<ul>
<div id='links'>
<!-- items will be populated here -->
</div>
</ul>
</div>
</div>
<!-- our template that will be used to generate new content within the above, regular HTML' -->
<template data-id='links'>
<span>
<center>
<p></p>
</center>
<div class='product'>
<img />
<a class='link'>Read More</a>
</div>
</span>
</template>
The traditional way to add content to the DOM on-the-fly is to use a series of calls to createElmenent and appendChild (which is less error-prone than just inserting HTML strings). And you can loop through your data object's keys and extract the details you need to configure your new DOM elements. This script does both of these things in a function called updateDOM, which invokes the appendProductDetails function once per product.
I changed the hrefs to create functional (if arbitrary) links, and of course the images don't show up because they don't exist on StackOverflow's server. See the in-code comments for further explanation.
const currentProds = getProductsToShow();
updateDOM(currentProds);
function updateDOM(prod_obj) {
// Identifies parent div
const linksDiv = document.getElementById("links");
// Clears parent div
linksDiv.innerHTML = "";
// Loops through productName (keys) in prod_obj
const productNames = Object.keys(prod_obj);
for (let productName of productNames) {
// Gets details (inner object) for each product
const details_obj = prod_obj[productName];
// Creates, configures, and appends new elements for each product
appendProductDetails(linksDiv, details_obj);
}
}
function appendProductDetails(parentElement, detailsObject) {
const
// Gets local copies of values via "destructuring"
{ title, image, href } = detailsObject,
path = "img/products/", // Defines path to images
// Creates elements to add to the DOM
productDiv = document.createElement("div"),
titleP = document.createElement("p"),
img = document.createElement("img"),
anchor = document.createElement("a");
// Configures newly created elements
productDiv.classList.add("product");
titleP.textContent = title;
img.src = path + image;
img.alt = image;
anchor.classList.add("link");
anchor.href = href;
anchor.textContent = "Read More";
// Puts children into productDiv
productDiv.appendChild(titleP);
productDiv.appendChild(img);
productDiv.appendChild(anchor);
// Attaches everything to the DOM
parentElement.appendChild(productDiv);
}
// Provides demo data
function getProductsToShow() {
const productsObj = {
"headphone_products": {
"title": "Steelseries",
"image": "h-steelseries.png", // In img/products/
"href": "https://stackoverflow.com"
},
"other_products": {
"title": "Other product",
"image": "h-other.png",
"href": "https://eloquentjavascript.net/"
}
};
return productsObj;
}
.container{ width: 250px; text-align: center; }
.dropdown > a{ text-decoration: none; }
p{ margin: -0.1rem 0; font-size: 1.2rem; }
.product{ padding: 0.5rem ; }
.link{ margin-left: 1rem; }
<div class="container">
<div class="dropdown">
PS5 HEADSETS
<div id="links"></div>
</div>
</div>
(A more modern approach would be to repeatedly clone the contents of a template element and to use slot elements to insert corresponding product details into each new instance.)

Array of images into storage

I am making little game in angular and i have problem how to export array with images to storage so when i reload the page it doesn't disappear. Array is made of random items from other array and adding next item is by pressing button.
That's my code in typescript
containers = [];
images = [
{ id: 0, name: "sword", url: "../../../../assets/img/sword.png" },
{ id: 1, name: "sword2", url: "../../../../assets/img/sword2.png" },
];
add() {
let index = Math.round(Math.random());
this.containers.push(this.images[index]);
}
and this is in html:
<button (click)="add()">Add</button>
<div id="content">
<div id="contentInside" *ngFor="let image of containers">
<img class="item" src="{{image.url}}" />
</div>
</div>
I want to keep it looking like this
You can store a copy of your image array in localstorage as
localStorage.setItem('images', JSON.stringify(this.images))
And retrive those images on browser reload by simply doing
private images = JSON.parse(localStorage.getItem('images')) || [];

Loading specific div based on the results of multiple dropdowns

EDIT: Would the approach be much easier if the Javascript listed was removed completely, and the dropdown menus restyled as <div>'s within <li>'s, and the final div was generated by a Javascript onclick event? e.g.
<a id="click_link">click me</a>
$("#click_link").click(function(){
$('#div').load('http://www.link.com/');
});
Either way, the problem at hand...
My decision to use an elegant-looking javascript solution is highlighting my massive inexperience when it comes to javascript! The problem is, on the face of it, simple...
Once an option has been chosen on each of the dropdown menus, I need a final div to load so that a specific button can be shown (a link to buy the item with the specified options, e.g. choosing Necklace D, with Stone Option B, and Delivery Option A = loading div with 'Buy' Button #17)
The dropdowns are divs that are filled and styled through the Javascript (as opposed to using the simpler <form> and <input> method), giving the flexibility to add two lines of differently styled text for each option etc. - This is where I step into the realm of the unknown and my inexperience shines through.
The isolated section is viewable in its entirity here
Ok, to the code.
Here's the Javascript:
function createByJson() {
var pearlData = [
{description:'Choose your pearl...', value:'Pearls', text:'Pearls'},
{description:'Beautiful black stone', value:'Black Pearl', text:'Black Pearl'},
{description:'Classic white stone', value:'White Pearl', text:'White Pearl'}
];
$("#DropItPearls").msDropDown({byJson:{data:pearlData, name:'pearls', width: 200}}).data("dd");
var blodeuweddData = [
{description:'Choose your item...', value:'Blodeuwedd', text:'the Blodeuwedd Collection'},
{description:'A striking statement', value:'BlodeuweddCelticStatement', text:'Celtic Statement Piece'},
{description:'Gold laced flower and pearl', value:'BlodeuweddBracelet', text:'Bracelet'},
];
$("#DropItBlodeuwedd").msDropDown({byJson:{data:blodeuweddData, name:'blodeuwedd', width: 250}})
.msDropDown({on:{change:function(data, ui) {
var val = data.value;
if(val!="")
window.location = val;
}}}).data("dd");
var deliveryData = [
{description:'Choose your method...', value:'Delivery', text:'Delivery Options'},
{description:'4-6 weeks delivery', value:'Four Weeks', text:'Made To Order'},
{description:'(unavailable on this item)', value:'Rush', text:'Express Delivery', disabled:true}
];
$("#DropItDelivery").msDropDown({byJson:{data:deliveryData, name:'delivery', width: 200, selectedIndex: 1}}).data("dd");
paymentData = [
{ description:'How would you like to pay?', value:'Payment', text:'Payment Method'},
{image:'images/msdropdown/icons/Visa-56.png', description:'Secure online payment', value:'Visa', text:'Visa'},
{image:'images/msdropdown/icons/Paypal-56.png', description:'Secure online payment', value:'Paypal', text:'Paypal'},
{image:'images/msdropdown/icons/EmailPay-56.png', description:'Order by email', value:'Email Payment', text:'Send Your Details'},
{image:'images/msdropdown/icons/Mastercard-56.png', description:'(coming soon)', value:'Mastercard', text:'Mastercard', disabled:true},
{image:'images/msdropdown/icons/Collect-56.png', description:'(coming soon)', value:'Collection', text:'Order and Collect', disabled:true},
{image:'images/msdropdown/icons/Email-56.png', description:'email Menna', value:'Other Method', text:'Alternatives'}
];
$("#DropItPayments").msDropDown({byJson:{data:paymentData, name:'payments', width: 250}}).data("dd");
}
$(document).ready(function(e) {
//no use
try {
var pages = $("#pages").msDropdown({on:{change:function(data, ui) {
var val = data.value;
if(val!="")
window.location = val;
}}}).data("dd");
var pagename = document.location.pathname.toString();
pagename = pagename.split("/");
pages.setIndexByValue(pagename[pagename.length-1]);
$("#ver").html(msBeautify.version.msDropdown);
} catch(e) {
//console.log(e);
}
$("#ver").html(msBeautify.version.msDropdown);
//convert
$("select").msDropdown();
createByJson();
$("#tech").data("dd");
});
function showValue(h) {
console.log(h.name, h.value);
}
$("#tech").change(function() {
console.log("by jquery: ", this.value);
})
//
And the html:
<div id="dropOptions">
<div id="dropOptionsTitle"><p>Item</p></div>
<div id="DropItBlodeuwedd"></div>
</div>
<div id="dropOptions">
<div id="dropOptionsTitle"><p>Precious Stones</p></div>
<div id="DropItPearls"></div>
</div>
<div id="dropOptions">
<div id="dropOptionsTitle"><p>Payment</p></div>
<div id="DropItPayments"></div>
</div>
<div id="dropOptions">
<div id="dropOptionsTitle"><p>Delivery</p></div>
<div id="DropItDelivery"></div>
</div>
<div id="dropOptions">
<div id="dropOptionsTitle"><p>Buy Now!</p></div>
<div id="DropItBuy"></div>
</div>
Again, working version viewable here
Many thanks in advance!
What I think you want is for your Buy button to dynamically read what the dropdowns currently say and build a link for redirection based on that, rather than trying to update the Buy button every time a dropdown changes.
From your code I can't see what the form of the final URL is supposed to be. For example, to get the current value of the delivery option, you can check $('#DropItDelivery :selected').text() which will be something like "Made To Order".
Your Buy Now! could be a button with a click event that reads these values and constructs the URL with basic string concatenation, e.g.:
window.location = "buynow.html?delivery=" + $('#DropItDelivery :selected').text() +
"&payment=" + $('#DropItPayments :selected').text()
// etc.
Of course you'd have to handle these options on the server.
In case you want to redirect to the payment page of the processor, you can just branch based on the payment method and give them the URL you want based on that.
var pm = $('#DropItPayments :selected').text();
if (pm == "Visa")
{
// Visa payment URL construction
}
else if (pm == "Send Your Details")
{
// Send your details URL construction
}
// etc.

How do I keep the original value selected when transitioning to edit mode?

The select lists are not rendering with the correct option selected. I've tried this a number of different ways including a computed selected observable (this.selected = ko.computed(return parseInt(selected(), 10) == this.id; )) and find in array functions.
In production, the dataArea elements would be populated with server side data. Using the divs with "data-" attributes keeps server side and client side scripting separate (I find this helps the designers).
A record would be displayed in non edit mode first with the option to edit by clicking the edit button. In edit mode, the initial values for the record appear in input controls. You would have the option to say, choose another customer and the having the form load new associated projects. Loading a new customer would reset the project list as expected.
So while loading a new customer would work well, its the transition to editing the current values that is causing an issue. The selected project needs to appear in the drop down list. If a new customer is chosen, the list populates with new options and no defaults are required.
http://jsfiddle.net/mathewvance/ZQLRx/
* original sample (please ignore) http://jsfiddle.net/mathewvance/wAGzh/ *
Thanks.
<p>
issue: When the select options are read, the inital value gets reset to the first object in the options. How do I keep the original value selected when transitioning to edit mode?
</p>
<div>
<h2>Edit Quote '1001'</h2>
<div class="editor-row" data-bind="with: selectedCustomer">
<label>Customer</label>
<div data-bind="visible: !$root.isEditMode()">
<span data-bind="text: CompanyName"></span>
</div>
<div data-bind="visible: $root.isEditMode()">
<input type="radio" name="customerGroup" value="1" data-bind="value: id"> Company Name 1
<input type="radio" name="customerGroup" value="2" data-bind="value: id"> Company Name 2
</div>
</div>
<div class="editor-row">
<label>Project</label>
<div data-bind="visible: !isEditMode()">
<span data-bind="text: selectedProject.Name"></span>
</div>
<div data-bind="visible: isEditMode()">
<select data-bind="options: selectedCustomer().projects, optionsText: 'Name', value: selectedProject"></select>
</div>
</div>
<div>
<button data-bind="click: function() { turnOnEditMode() }">Edit</button>
<button data-bind="click: function() { turnOffEditMode() }">Cancel</button>
</div>
</div>
<hr/>
<div data-bind="text: ko.toJSON($root)"></div>
function ajaxCallGetProjectsByCustomer(customerId) {
var database = '[{"CustomerId": 1, "Name":"Company Name 1", "Projects": [ { "ProjectId": "11", "Name": "project 11" }, { "ProjectId": "12", "Name": "project 12" }, { "ProjectId": "13", "Name": "project 13" }] }, {"CustomerId": 2, "Name": "Company Name 2", "Projects": [ { "ProjectId": "21", "Name": "project 21" }, { "ProjectId": "22", "Name": "project 22" }, { "ProjectId": "23", "Name": "project 23" }] }]';
var json = ko.utils.parseJson(database);
//console.log('parseJson(database) - ' + json);
//ko.utils.arrayForEach(json, function(item) {
// console.log('CustomerId: ' + item.CustomerId);
//});
return ko.utils.arrayFirst(json, function(item){
return item.CustomerId == customerId;
});
}
var Customer = function(id, name, projects) {
var self = this;
this.id = ko.observable(id);
this.CompanyName = ko.observable(name);
this.projects = ko.observableArray(ko.utils.arrayMap(projects, function(item) {
return new Project(item.ProjectId, item.Name);
}));
};
Customer.load = function(id) {
var data = ajaxCallGetProjectsByCustomer(id);
var customer = new Customer(
data.CustomerId,
data.Name,
data.Projects);
};
var Project= function(id, name) {
this.id = id;
this.Name = ko.observable(name);
};
var QuoteViewModel = function () {
var self = this;
$customerData = $('#customerData'); // data from html elements
$projectData = $('#projectData');
// intial values to display from html data
var customer = new Customer (
$customerData .attr('data-id'),
$customerData .attr('data-companyName'),
[{"ProjectId": $projectData .attr('data-id'), "Name": $projectData .attr('data-name')}]
)
this.selectedCustomer = ko.observable(customer);
this.selectedProject = ko.observable($projectData.attr('data-id'));
this.isEditMode = ko.observable(false);
this.selectedCustomer.subscribe(function(){
// todo: load customer projects from database api when editing
});
this.turnOnEditMode = function() {
var customerId = self.selectedCustomer().id();
console.log('customerId: ' + customerId);
Customer.load(customerId);
self.isEditMode(true);
};
this.turnOffEditMode = function() {
self.isEditMode(false);
};
};
var viewModel = new QuoteViewModel();
ko.applyBindings(viewModel);
One the initial value you load
this.dongle = ko.observable($dongleData.attr('data-id'));
This would be the string value "3". Where as the dongle html select element is actually saving/expecting to retrieve the object { "Id": "3", "Name": "dongle 3" }.
Here is a working version that gets the correct initial values and allows editing.
http://jsfiddle.net/madcapnmckay/28FVr/5/
If you need to save the a specific value and not the whole dongle/widget object, you can use the optionsValue attribute to store just the id. Here is it working in the same way.
http://jsfiddle.net/madcapnmckay/VnjyT/4/
EDIT
Ok I have a working version for you. I'll try to summarize everything I changed and why.
http://jsfiddle.net/madcapnmckay/jXr8W/
To get the customer info to work
The Customer name was not stored in the ajaxCallGetProjectsByCustomer json so when you loaded a customer there was no way to determine the new name from the data received. I added a Name property to each customer in the json with name "Company Name 1" etc.
To get the projects collection to work
The problem here was as stated originally with the dongles. You initialize the selectedProject observable with $projectData.attr('data-id') which equates to string value of 13. This is incorrect as the select list is configured in such a way that it actually saves/expects to receive the project object itself. Changing this id assignment to an object assignment made the initial value of project work correctly.
var project = ko.utils.arrayFirst(customer.projects(), function(project){
return project.id == Number($projectData.attr('data-id'));
});
this.selectedProject = ko.observable(project);
FYI there was a minor error in the html, the selectedProject.Name needed to be selectedProject().Name. No big deal.
I'm sure you could have figured out those pretty easily. The next bit is where the real issue is. You reload the Customer every time the edit button is clicked. This seems strange and you may want to reconsider that approach.
However what happens is you load a customer object from the server by id. Assign it to the selectedCustomer observable, this actually works fine. But then because the drop down is bound to selectedCustomer().projects and viewModel.selectedProject it expects that selectedProject is a member of selectedCustomer().projects. In the case of objects the equality operator is assessing whether the references match and in your case they do not because the original selectedProject was destroyed with its associated customer when you overwrote the selectedCustomer value. The fact that the ids are the same is irrelevant.
I have put in place a hack to solve this.
var oldProjectId = viewModel.selectedProject().id;
viewModel.selectedCustomer(customer);
var sameProjectDifferentInstance = ko.utils.arrayFirst(customer.projects(), function(project){
return project.id == oldProjectId;
});
viewModel.selectedProject(sameProjectDifferentInstance || customer.projects()[0]);
This saves the old projectId before assigning the new customer, looks up a project object in the new customer object and assigns it or defaults to the first if not found.
I would recommend rethinking when you load objects and how you handle their lifecycle. If you hold the current objects it memory with a full list of projects included you don't need to reload them to edit, simply edit and then send the update back to the server.
You may find it easier to hold json from the server in js variables instead of html dom elements. e.g.
<script>var projectInitialData = '#Model.ProjectInitialData.toJSON()';</script>
Hope this helps.

Categories

Resources