Handle HTML code block as an object? - javascript

I have a div that basically represents a book (so a nice div layout with an image of the book, title, price, red background if on sale etc.). So what i do is to get the properties of a book from the database, insert the values in kind of an html template and display it.
Now, once it is displayed i hate how i have to handle the data. I have to either parse css properties to figure out if a book is on sale for an example or i have to keep the data in another place as well (some javascript array or use the jquery data feature). The first option is ugly the second one would require me to update two things when one property changes - which is ugly as well.
So what i would like is to handle that block of html (that represents a single book) as an object. Where i can call obj.setPrice(30); and things like that and finally call obj.update(); so it would update its appearance.
Is there anyway to accomplish this ? Or something like this ? I just feel that once i render the data as html i loose control over it :(

Suppose your html div is like this
<div id="book1">
<div id="price">$30</div>
...
</div>
You can define a Book object as follows:
var Book = function(name) {
this.name = name;
}
Book.prototype = {
setPrice : function(price) {
this.price = price;
},
update : function() {
pricediv = document.getElementById(this.name)
pricediv.innerHTML = '$'+price;
}
}
var book = new Book('book1')
book.setPrice(50);
book.update();

I guess your best shot is write your own object / methods for that.
var Book = function(){
var price = args.price || 0,
color = args.color || 'red',
height = args.height || '200px',
width = args.width || '600px',
template = "<div style='background-color: COLOR; width=WIDTH; height: HEIGHT;'><span>$PRICE</span><br/></div>";
return {
setPrice: function(np){
price = np;
return this;
},
setColor: function(nc){
color = nc;
return this;
},
setHeight: function(nh){
height = nh;
return this;
},
render: function(){
template = template.replace(/COLOR/, color);
template = template.replace(/PRICE/, price);
// etc
// use jQuery or native javascript to form and append your html
$(template).appendTo(document.body);
}
};
};
This is just a pretty basic example which can be optimized like a lot. You may even think about using John Resigs microtemplate (http://ejohn.org/blog/javascript-micro-templating/)
Usage from the above example would look like:
var book = Book({
price: 30,
color: 'blue'
});
book.render();
To change values:
book.setPrice(140).setColor('yellow').setHeight('500').render();

I have been playing around with Microsoft's proposal for jQuery Templates and Data Linking and so far it's going awesome.
TLDR, checkout this demo.
It's extremely easy to just link up a piece of HTML with a JavaScript object and from thereon, only update the JavaScript object and the HTML updates automatically.
Here's a simple example. Create the HTML that will represent your widget.
<div class="book">
<img width="100" height="100" src="" />
<div class="title"></div>
<div class="price"></div>
</div>
Then create a JavaScript object and dynamically link it to the HTML above. Here is a sample object:
var book = {
title: "Alice in Wonderland",
price: 24.99,
onSale: true,
image: "http://bit.ly/cavCXS"
};
Now onto the actual linking part. The items we are going to link up are:
A data-onsale attribute in the outer div which will be either "true" or "false"
The image src attribute to the image property of our book
title div to the title property
price div to the price property
The following sets up the linking. Note that we are only doing a one way linking here, but it's possible to setup a two way linking also using the linkBoth function.
$(book)
.linkTo('title', '.title', 'html')
.linkTo('price', '.price', 'html')
.linkTo('image', '.book img', 'src')
.linkTo('onSale', '.book', 'data-onsale')
That's it. From now onwards, just update the book object and the HTML will automatically update. Update the properties of the book like you would update html attributes using the attr function.
$(book).attr({
price: 14.75
});
or
$(book).attr('price', 14.75);
The code above is only using Data Linking, but the proposal also mentions combining data linking with templates which would make this even more easier. From what I reckon, you would be able to do this and get the above functionality:
<script id="bookTemplate" type="text/html">
<div class="book" data-onsale="{{link onSale }}">
<img width="100" height="100" src="{{link image }}" />
<div class="title">{{link title }}</div>
<div class="price">{{link price }}</div>
</div>
</script>
Link the above template with the book object in one step and add it to the page:
$('#bookTemplate').render(book).appendTo('body')
Update the properties of the book object, and changes will reflect.

Related

How to dynamically change info modal innerHTML for each clicked element from an array

I am trying to make a small collection of recipes and four of them are already stored inside an array of objects, each object representing another recipe. My problem is that I want to make an info window, a modal if you will, which will show information about each clicked recipe that's stored inside its object.
The thing is whenever i try to set innerHTML of said modal the for loop I created shows entire object and so far I didn't find out how to make each click on modal show only the info for one recipe. (First link should show the details for the first recipe, second for the second and so on).
I tried a for loop which should dynamically loop content for the info window depending on the clicked element but it shows the entire object and so far I'm not sure what other method would be a better solution.
My array of objects looks like this
var recipes = [
{
key: 0,
title: 'Pasta Carbonara',
ingredients: 'etc',
instructions: 'etc'
},
{
key: 1,
title: 'etc',
ingredients: 'etc',
instructions: 'etc'
},
and so on (4 objects)
]
and my for loop looks like this:
function openModal() {
detailsModal.style.display = 'block';
var modalContent = document.getElementById('modalInfo');
var modalBody = '';
for (var i=0; i < recipes.length; i++){
modalBody += JSON.stringify(recipes[i])
}
modalContent.innerHTML = modalBody;
}
The entire code's here: https://codepen.io/Ellie555/pen/KOwexB
This question is really mundane but if you had any suggestions I'd appreciate it.
One way would be to add data attributes to the anchors:
Pasta Carbonara
And then using those attributes to instruct your modal code which recipe to load:
function openModal(e) {
detailsModal.style.display = 'block';
var modalContent = document.getElementById('modalInfo');
// The critical line:
var modalBody = JSON.stringify(recipes[parseInt(e.currentTarget.dataset.recipeIndex)]);
modalContent.innerHTML = modalBody;
}
Full code: https://codepen.io/mac9416/pen/BXyPdO
Aside: I would use <button> elements styled as links instead of anchors for accessibility.
Your markup above isn't semantic html since you're not redirect or navigating. So first of all I'd replace ... tag with <button type="button">...</button>:
<div class="main">
<div class="recipes" id="recipeSection">
<div class="recipe-entry">
<div class="name"><button type="button" id="0">...</button></div>
</div>
<div class="recipe-entry">
<div class="name"><button type="button" id="1">...</button></div>
</div>
<div class="recipe-entry">
<div class="name"><button type="button" id="2">...</button></div>
</div>
<div class="recipe-entry">
<div class="name"><button type="button" id="3">...</button></div>
</div>
</div>
</div>
To answer your question to dynamically change info modal innerHTML for each clicked element from an array!
add id to each element that will be clicked to associate it with the desired object in your array
filter that array based on the click target with its id
const data = recipes.filter(recipe => recipe.key === Number(event.target.id));
modalContent.innerHTML = JSON.stringify(data[0]);
I forked and modified your code. Here's a working Demo.
Note:
If you're not sure about key values in your array for each item (i.e. dynamically) you can iterate over it and append it into your DOM.
You can also create list from Javascript instead statically create it in HTML.
My code with comments here:
https://stackblitz.com/edit/js-bu6tz8?file=index.js

Better way to displaying data from view model using knockoutjs?

I have a view model with a separate hierarchy and during, say a click event, I'd like to display a modal dialog of the data from the secondary hierarchy for the "clicked" data item. To make this a little easier to follow, I have mocked up an example in jsfiddle that achieves the desired result (without a modal for simplicity), but it's done by repeating the same markup instead of modifying the data from a single group of markup.
var FoundingFathersViewModel = function(data) {
var self = this;
self.foundingFathers = ko.observableArray([]);
//click
self.detail = function(father) {
//get the selected Founding Father's positions HTML and
//set the HTML of the detail div
var html = $('#'+father.id()).html();
$('#detail').html(html);
};
var mapping = $.map(data, function(item) { return new FoundingFather(item); });
self.foundingFathers(mapping);
};
var FoundingFather = function(item) {
this.id = ko.observable(item.id);
this.name = ko.observable(item.name);
this.positions = ko.observableArray(item.positions);
};
ko.applyBindings(new FoundingFathersViewModel(data));
The jsfiddle code simply modifies the CSS display property to display the correct detail. I'd like to think there would be a "cleaner" way to do this. Any help would be appreciated. And if there's not a more elegant solution, I'd like to know that too.
https://jsfiddle.net/jvz6gktm/2/
I'd suggest moving towards a selectedItem approach. Clicking on a president sets that one as the selected item, which is what populates the detail section.
see my updated fiddle:
https://jsfiddle.net/jvz6gktm/5/
detail area:
<div id="detail" data-bind="with: selectedPresident">
<div class="positions">
<div data-bind="foreach: positions" class="detail">
<h5 data-bind="html: position" />
<h6 data-bind="html: yearsActive" />
</div>
</div>
</div>
selection:
self.selectedPresident = ko.observable();
self.selectPresident = function(father) {
self.selectedPresident(father);
};
As a side note: if you're using knockout, don't use jQuery to go messing with the DOM, you're just asking for trouble.

Iterate a function in ANGULAR

I am studying Angular. I am making an application: an user choose from one html selection, then fill two input fields. After this, the user press Update and the barcode script generates the code image with 3 parameters: the first select and the two input. ( these three are separated by some spaces ). So far, no problem.
I've added the button for add new forms, and the json array save the input correctly. I wanted to generate a barcode for each compiled form. How can i do? This is an easy example of what i am doing:
http://plnkr.co/edit/hxZb6g9tkwN0zpRmOMjw?p=preview
at the end of html you can find the script of barcode:
<div class="ean">
<img id="barcodeImage" style="border: solid 1px black;"/>
<script type="text/javascript">
function updateBarcode()
{
var barcode = new bytescoutbarcode128();
var space= " ";
var value = document.getElementById("barcodeValue").value;
var value1 = document.getElementById("barcodeValue1").value;
var value2 = document.getElementById("barcodeValue2").value;
barcode.valueSet(value + space + value1 + space + value2);
barcode.setMargins(5, 5, 5, 5);
barcode.setBarWidth(2);
var width = barcode.getMinWidth();
barcode.setSize(width, 100);
var barcodeImage = document.getElementById('barcodeImage');
barcodeImage.src = barcode.exportToBase64(width, 100, 0);
}
</script>
You're definitly not doing this the angular-way : mixing angular code and plein javascript, as you're doing, is generally not a good idea. It would be a good idea to write an custom directive which bundle your barcode library.
Anyway, your updateBarcode function is getting its values directly from the html input fields (eg. document.getElementById("barcodeValue").value) and writing directly its results into the DOM. With angular you may not manipulate the DOM directly but use your controller's scope (eg. $scope.foods).
To fix this, you can move your updateBarcode function into your angular controller and create an html container four your resulting images, achieving something like this :
app.controller('ProductController', function($scope,$http) {
$scope.foods = [ { ... } ]
...
$scope.updateBarcode = function() {
...
angular.forEach($scope.foods, function(food) {
var value = food.selectproduct;
var value1 = food.Quantity1;
var value2 = food.Quantity2;
...
// here, i'm not sure the following code will work as it is. If not, you'd better use a directive and angular.element()
// but here is the general concept...
var barcodeContainer = document.getElementById('barcodeContainer');
var img = document.createElement("img");
img.src = barcode.exportToBase64(width, 100, 0);
barcodeContainer.appendChild(img)
}
}
}
Then change your html accordingly :
<input type="button" value="Update" onclick="updateBarcode()" />
to:
<input type="button" value="Update" ng-click="updateBarcode()" />
and
<img id="barcodeImage" style="border: solid 1px black;"/>
to:
<style type="text/css">
#barcodeContainer img {
border: solid 1px black;
}
</style>
<div id="barcodeContainer">
</div>
You should use the Angular way! Don't mix plain Javascript with Angular. This will be misleading. BTW. You shouldn't use ids in a list. Id's should be unique. The function document.getElementById will always return the first element it finds with this id. This way you'll never find the other elements.
Create the barcode for each item in the list and angular will bind the generated barcode to the image.
$scope.updateBarcode = function(food) {
var barcode = new bytescoutbarcode128();
barcode.valueSet([food.selectedproduct,food.Quantity1,food.Quantity2].join(" "));
barcode.setMargins(5, 5, 5, 5);
barcode.setBarWidth(2);
var width = barcode.getMinWidth();
barcode.setSize(width, 100);
food.barcodeSrc = barcode.exportToBase64(width, 100, 0);
};
http://plnkr.co/edit/4scoibxyZ1EgJiRMex1V?p=preview
Have a look at this plukr (forked from your sample), and use class instead of id:
<img class="xxx" code-index="{{$index}}">
Basically what you need is to put the image inside the repeater and find a way to address it.
But since you are learning angular, why not turn everything into a single component and use ng-click instead of onclick?

Using html <template>: innerHTML.replace

The most popular intro says that I can easily clone html templates within my document.
<template id="mytemplate">
<img src="" alt="great image">
<div class="comment"></div>
</template>
The word "template" however implies that you won't copy-paste it as is, unmodified. Template means that you want to update some variables with specific values. It recommends the following approach for updating the node:
var t = document.querySelector('#mytemplate');
// Populate the src at runtime.
t.content.querySelector('img').src = 'logo.png';
var clone = document.importNode(t.content, true);
document.body.appendChild(clone);
Isn't it perfect? There is querySelector to get the element so that you can update its attributes. I just do not understand why does he updates the template before cloning it. But that is not my question. The real question is that, in mine case, the location of variable to update is unknown. It can be either attribute or innerText in whatever template structure is. This is the most general and frequent use of template, I believe. So, I can ensure that the variable id is unique within the template, like #reply here
<template id="comment-template">
<li class="comment">
<div class="comment-author"></div>
<div class="comment-body"></div>
<div class="comment-actions">
Reply
</div>
</li>
</template>
ought to update the #reply, but author does not explain how to do that. I succeeded to use innerHTML on the original template, document.querySelector('#mytemplate').innerHTML.replace(id, value) but this breaks the template for later use, as explained above. I failed to update the cloned text. This is probably because template.clone produces a document fragment, which has no innerHTML. But, before pushing that forth, I decided to investigate for alternatives since I know that innerHTML/outerHTML is not quite standard.
Alternative for innerHTML? inspects the alternatives to innerHTML but again, they assume too much about the template. Instead of just replacing some specific identifiers with user values, they completely recreate the template, which defeats the whole notion of template. Template looses any sense once you recreate its whole code in the variable valuation. So, how is <template> is supposed to use?
Here is mine solution, the valuate function
<div id="div1">env: </div>
<template id="template1">
var1=var2
</template>
<script language="javascript">
function valuate(template, targetParent, dict) {
var t = document.querySelector('#' + template);
var clone = t.cloneNode(true)
for (key in dict) {
clone.innerHTML = clone.innerHTML.replace(key, dict[key])
}
var fragment = document.importNode(clone.content, true)
var canvas = document.querySelector('#' + targetParent);
canvas.appendChild(fragment);
//alert(canvas.innerHTML)
}
valuate("template1", "div1", {var1:"1+1", var2:2, "#ref":"abc.net"})
</script>
It takes the template, dictionary and target element. Unfortunately, it fails for SVG hierarchies. Don't you know why?
Using again <template>.querySelectorAll to select the element and setAttribute to change the href value
var a = <template>.querySelectorAll("a[href='#reply']");
a[0].setAttribute("href", <url>)
This is a more generic function. It changes an attribute of the selected elements within a cloned template, of course this is very basic,
//object = the cloned template.
//selector = the selector argument which selects all nodes.
//attribute = the attribute to change.
//value = the value that needs to be set to the attribute.
function changeTemplateValues(object, selector, attribute, value)
{
elements = object.querySelectorAll(selector);
if (elements.length == 0)
{
return false; //stop executing. No elements were found.
}
else if (!attribute && !value)
{
return elements; //no attributes and values are set, return nodelist;
}
else
{
if (attribute)
{
//loop over all selected elements to change them.
for (var i = 0; i < elements.length; ++i)
{
if (attribute.match(/innerHTML|textContent|text|nodeValue/g) )
{
elements[i][attribute] = value;
}
else
{
elements[i].setAttribute(attribute, value);
}
}
}
else
{
//No attribute return false;
return false;
}
}
}

WinJS.UI.ListView - refreshing items when using template is built using javascript

I've got a ListView that was using HTML-defined templates like this:
<div id="mediumListIconTextTemplate" data-win-control="WinJS.Binding.Template">
<div>
<!-- Displays the "picture" field. -->
<img data-win-bind="alt: title; src: picture" />
<div>
<!-- Displays the "title" field. -->
<h4 data-win-bind="innerText: title"></h4>
<!-- Displays the "text" field. -->
<h6 data-win-bind="innerText: description"></h6>
</div>
</div>
</div>
<div id="basicListView" data-win-control="WinJS.UI.ListView"
data-win-options="{itemDataSource : DataExample.itemList.dataSource, itemTemplate: select('#mediumListIconTextTemplate')}">
</div>
When my list items changed, my item template would be updated to reflect the change. However, out of need, I had to change to using a javaScript function to build my template. I modeled my code after the code found on the sample site:
app.onactivated = function (args) {
if (args.detail.kind === activation.ActivationKind.launch) {
if (args.detail.previousExecutionState !== activation.ApplicationExecutionState.terminated) {
// TODO: This application has been newly launched. Initialize
// your application here.
} else {
// TODO: This application has been reactivated from suspension.
// Restore application state here.
}
args.setPromise(WinJS.UI.processAll().then(function () {
var lView = document.getElementById("templateFunctionListView").winControl;
lView.itemTemplate = itemTemplateFunction;
}));
}
};
function itemTemplateFunction(itemPromise) {
return itemPromise.then(function (item) {
var div = document.createElement("div");
var img = document.createElement("img");
img.src = item.data.picture;
img.alt = item.data.title;
div.appendChild(img);
var childDiv = document.createElement("div");
var title = document.createElement("h4");
title.innerText = item.data.title;
childDiv.appendChild(title);
var desc = document.createElement("h6");
desc.innerText = item.data.text;
childDiv.appendChild(desc);
div.appendChild(childDiv);
return div;
});
};
After changing to the javascript function, my display items never change when my binding data changes.
What do I need to do to make them update?
I think there are two approaches that could work for you.
In my case, when I refresh the data for my app, it's possible that one or more entities may be completely out of date, and there may be new entities to display. So I simply re-set the binding of the ListView, like so:
listView.itemDataSource = Data.items.dataSource;
where Data is the namespace I set up in data.js to contain all my data functions and objects.
When I update the value of the itemDataSource property, the ListView will re-bind to the new data, and display the correct items.
The other thing to look at, if your data is only being updated one property at a time, is using the WinJS.Binding.as function to make the items in the binding list observable, as described here:
http://msdn.microsoft.com/en-us/library/windows/apps/hh781224.aspx#updating_changing_records
If you haven't seen it already, there's some good info on databinding here:
http://msdn.microsoft.com/en-us/library/windows/apps/hh758311.aspx
And I found the following MSDN forum thread that may be helpful:
http://social.msdn.microsoft.com/Forums/en-US/winappswithhtml5/thread/21b9603f-e28d-4c93-b164-a2c91ba5c4ca
Hope the above helps!
For more information on Windows Store app development, register for App Builder.
Instead of rebinding the whole data set you can just rebind the single item. Find the items position in the list and then splice it replacing it with itself.
for (var i = 0; i < list.length; i++){
item = list.getAt(i);
if (item.key == itemToBeReBound.key){
list.splice(i, 1, item);
i = list.length;
}
}

Categories

Resources