JsViews: UI element with wrong values after observable refresh - jsviews

JsViews is a great library. And i have been working with it for some time now.
But still i can't find why the hell this event call won't work.
The data after the sorting is right and the arr is good.
sortTiers: function () {
var arr = this.StructureActivitiesTiers.slice()
.sort(function (a, b) { return parseFloat(a.TierTo) - parseFloat(b.TierTo); });
for (var i = 0; i < arr.length; i++) {
arr[i].TierTo = parseFloat(arr[i].TierTo);
arr[i].TierFrom = (i == 0 ? 0 : arr[i - 1].TierTo)
}
$.observable(this.StructureActivitiesTiers).refresh(arr);
}
The template:
{^{for StructureActivities}}
<tr>
<td><input type="text" data-link="Currency"></td>
<td><input type="text" data-link="Price"></td>
</tr>
{^{for StructureActivitiesTiers}}
<tr>
<td></td>
<td>From: <input disabled data-link="TierFrom" type="text">  
To: <input type="text" data-link="TierTo">
</td>
</tr>
{{/for}}
<tr>
<td>
<button data-link="{on 'click' ~sortTiers}"/>
</td>
<td>
</td>
</tr>
{{/for}}
Thanks!

Ah OK. What you are doing is first sorting the StructureActivitiesTiers array, then modifying its values non-observably, then calling $.observable(this.StructureActivitiesTiers).refresh(arr);.
The effect of the refresh(arr) is to first determine if the objects in the new array, arr actually reference existing objects in StructureActivitiesTiers array. If so it will simply shuffle the rendered views to put them in the right order, and will not re-render each of them. So the non-observable property changes are ignored.
You can fix it by making the property changes observable too, either before or after doing the array refresh call:
for (var i = 0; i < arr.length; i++) {
$.observable(arr[i]).setProperty({
TierTo: parseFloat(arr[i].TierTo),
TierFrom: i == 0 ? 0 : arr[i - 1].TierTo
});
}
$.observable(this.StructureActivitiesTiers).refresh(arr);
or:
$.observable(this.StructureActivitiesTiers).refresh(arr);
for (var i = 0; i < arr.length; i++) {
$.observable(arr[i]).setProperty({
TierTo: parseFloat(arr[i].TierTo),
TierFrom: i == 0 ? 0 : arr[i - 1].TierTo
});
}
See https://jsfiddle.net/ertsu5qc/4/.
Alternatively you can fill the arr with new objects:
for (var i = 0; i < arr.length; i++) {
arr[i] = {
TierTo: parseFloat(arr[i].TierTo),
TierFrom: i == 0 ? 0 : arr[i - 1].TierTo
};
}
$.observable(this.StructureActivitiesTiers).refresh(arr);
Now the refresh(arr) call finds new objects, not references to existing ones, so it removes the old views for each item and inserts new ones for the new items, (so obviously renders them fully).
See https://jsfiddle.net/ertsu5qc/5/.

Related

Making a button that shows or hides multiple images in a random location

I have a problem when I am making the website for one gallery.
I made the code for the button that can show and hide multiple images.
I intend to make the button can place several images in randomly.
I write the code that can function for only one image.
Please tell me the code that functions as a button to place multiple images in a random location.
Users can hide images by pressing the button.
And when users press the button again, it places the images in another random location.
const btn = document.querySelector("button");
const height = document.documentElement.clientHeight;
const width = document.documentElement.clientWidth;
const box = document.getElementById("color");
btn.addEventListener("click", () => {
let randY = Math.floor((Math.random() * height) + 1);
let randX = Math.floor((Math.random() * width) + 1);
box.style.top = randY + "px";
box.style.right = randX + "px";
});
function showhide() {
var x = document.querySelectorAll("#color");
var i;
for (i = 0; i < x.length; i++) {
if (x[i].style.display === "block") {
x[i].style.display = "none";
} else {
x[i].style.display =
"block";
}
}
}
body {
height: 500px;
}
.random {
position: absolute;
}
<button onclick="showhide()" value="Zeige Features" id="button">click me</button>
<img id="color" style="display: none;" class="random" src="http://lorempixel.com/200/200/">
<img id="color" style="display: none;" class="random" src="http://lorempixel.com/200/200/">
You're doing the correct thing in showHide() when using querySelectorAll. You are then able to get all images.
You should never have elements with the same ids. They should be unique. So querySelectorAll("#color") works, but it's now how you should do. Do a querySelector on "img.random" instead.
getElementById only returns a single element, not like querySelectorAll. So you need to use querySelectorAll('img.random').
This might be beyond your knowledge, I don't think you should add the images in HTML, but in javascript code.
a) Add all image paths in an array: ['https://image.com/image.png', ...]
b) Add a single img element. <img id="template" class="random">
c) In javascript code, clone that element for each image path in the array. You can use cloneNode for this.
d) Randomize each position for each element, just like you have done now.
e) Add each element to the DOM through appendChild. Have a unique div that you append to. Be sure to clear it every time second time you hit the button.
f) Solve all bugs along the way. :P
The problem
The main issue here is that you're using getElementById to query #color
const box = document.getElementById("color");
Since getElementById only returns one element (but you have two in your DOM) and the style only applies to one element. That's why you're seeing only one element is randomly moving and the other just stay in the same place.
A side note here, id should be unique in a DOM.
You're in fact using the correct API for the job in the showhide function
var x = document.querySelectorAll("#color");
The fix:
To fix this, you need to query all images by their classname (as suggested in the side note, don't use id for the job)
const boxes = document.querySelectorAll(".random");
Now we have a node list, as you do in the showhide function, we need to loop thru it, I'm not using a for loop here, instead, a forEach loop, it's just more terser and a modern addition to the JS
// Since boxes are not array, we need to covert it to array so we can use that handy `.forEach` here:
Array.from(boxes).forEach(box => {
box.style.top = Math.floor((Math.random() * height) + 1) + "px";
box.style.right = Math.floor((Math.random() * width) + 1) + "px";
})
Now, this should fix your issue. See the complete code below.
const btn = document.querySelector("button");
const height = document.documentElement.clientHeight;
const width = document.documentElement.clientWidth;
const boxes = document.querySelectorAll(".random");
btn.addEventListener("click", () => {
Array.from(boxes).forEach(box => {
box.style.top = Math.floor((Math.random() * height) + 1) + "px";
box.style.right = Math.floor((Math.random() * width) + 1) + "px";
})
});
function showhide() {
var x = document.querySelectorAll(".random");
var i;
for (i = 0; i < x.length; i++) {
if (x[i].style.display === "block") {
x[i].style.display = "none";
} else {
x[i].style.display =
"block";
}
}
}
body {
height: 500px;
}
.random {
position: absolute;
}
<button onclick="showhide()" value="Zeige Features" id="button">click me</button>
<img id="color" style="display: none;" class="random" src="http://lorempixel.com/200/200/">
<img id="color" style="display: none;" class="random" src="http://lorempixel.com/100/100/">

HTML.DisplayFor method using string

In my ASP.Net MVC appication I have a loop which i'd like to display different Properties values in HTML using the HTML.DisplayFor method. But it will not accept a string value to direct to the Model parameter.
Normal mode:
<img src="#Html.DisplayFor(model => model.Image_1)" />
My loop:
#for (int i = 1; i < 11; i++)
{
string currentImage = "Model.Image_" + i;
if ((#Html.DisplayFor(model => "Model.Image_" + i ).ToString()) != "")
{
<div>
<img src="#Html.DisplayFor(model => currentImage)" />
</div>
}
}
My results in the img src are just currentImage.
I'd like it to be Model.Image_" + i.
How would I do that?
If you have a better way of doing this that would be appreciated as well.
Should my images have their own Model -class, you think?
You can get the value of a property dynamically using reflection like this.
#for (int i = 1; i < 11; i++)
{
string imagePropName= "Image_" + i;
string imageSrc = Model.GetType().GetProperty(imagePropName).GetValue(Model) as string;
if (!string.IsNullOrEmpty(imageSrc))
{
<div>
<img src="#Url.Content(imageSrc)" />
</div>
}
}
Another alternative would be to write <img src="#Url.Content(Model.Image_1), <img src="#Url.Content(Model.Image_2) /> 10 times. I mean if you have created 10 properties, might as well be consistent with the implementation.
But as stephen said, just get a List of ImagePaths and loop through them in your View. If the requirement changes to displaying 20 images, you'd have to add 10 more properties.

How to add different css class dynamically in a loop every 3 times

Basically what i need to do is looping all the posts and add css to article class like "cb-no-1" and the second one "cb-no-2" and the third one "cb-no-3" and repait it again until there's no post.
#foreach (var post in Model.Posts)
{
//First iteration
<article class= "cb-no-1">
</article>
//Second iteration
<article class= "cb-no-2">
</article>
//Third iteration
<article class= "cb-no-3">
</article>
//Fourth iteration
<article class= "cb-no-1">
</article>
//and so on.
}
How to achive it. Thanks in advance.
Use a variable and increment that in the loop and use this variable name to build the class name. When the variable reaches 3 (or your limit), reset it to initial value.
#{
var counter = 0;
}
#foreach (var post in Model.Posts)
{
if (counter >= 3)
{
counter = 0;
}
<article class= "cb-no-#(++counter)">#post.Title</article>
}
use a variable like as i inside the loop which is inited from 1 and increases in each loop and when it reachs the 4, reset it to 1
then use it for class name
Shyju, the first responder, has the right idea, but there are two modifications you need to make to his answer.
First, his code gives you a variable from 0 to 2. you asked for 1 to 3,. so the initialization needs to be "var counter = 1" and the "if" test needs to be just greater than 3, not greater than or equal to 3. Or you could change the 3 to a 4, such as: if (counter >= 4).
The second item left off of that code snippet is something to increment the counter. I'm not sure which dialect or language you are writing in but something like:
Counter+=1
or
counter = counter + 1
Modified, that code snippet would look like:
var counter = 1;
#foreach (var post in Model.Posts)
{
if (counter >= 4)
{
counter = 0;
}
<article class= "cb-no-#(++counter)">#post.Title</article>
counter += 1
}
Again, I do not know which language you are writing in so check my syntax carefully.

How can I apply Angular and UI-Sortable with to table cells without awkward spacing?

I want a table sortable by column using Angular UI-Sortable. When there are multiple rows in the table, the spacing gets confused. I think this is because the dragged cell and the placeholder are taking up space, even though the dragged cell is absolutely positioned.
My table looks like this:
<table>
<thead>
<tr ui-sortable="{'ui-floating': true, axis: 'x'}" ng-model="list">
<th ng-repeat="item in list">
{{item.text}}
</th>
</tr>
</thead>
<tbody>
<tr>
<td ng-repeat="item in list">
{{item.text}}
</td>
</tr>
</tbody>
</table>
and my controller looks like this:
var myapp = angular.module('sortableApp', ['ui.sortable']);
myapp.controller('sortableController', function($scope) {
var tmpList = [];
for (var i = 1; i <= 6; i++) {
tmpList.push({
text: 'Item ' + i,
value: i
});
}
$scope.list = tmpList;
});
I also have an example on codepen. Try dragging the first cell to see the rest of the row shift down past the end of the table.
I'm running Angular v1.4.9 and angular-ui-sortable v0.14.0.
I figured out a workaround. By adding an empty cell to each row, and adding ng-show to the first one, I get a cell to remove when the placeholder is added. This works pretty well, but the empty cell does take up a little space, and it makes the html a little less clear.
Updated codepen here: http://codepen.io/anon/pen/mPZBxq

Resize font depending on string length

I have a vertical menu and i want to make it localizable, but localized strings in menu elements often goes out off the edge.
So the question is how to make font resizable depending on the string length in CSS. And if possible, without JavaScript.
Thanks!
UPD: JQuery isn't acceptable. Any way in Pure JS?
You should make familiar with using plugins, they save you much time and of course they're very reliable (they are written by experienced scripters/programmers and have been tested by community). However looks like you want some pure JS solution. I've just made this code for you. It works fairly OK (although I'm not sure if it's as good as some plugins). The only requirement is the element (which you want to adjust the font-size accordingly to the text length) should contain plain text, not some HTML code.
The idea to implement it using pure JS is simple, you need some dummy element created using script, this dummy element is used to measure the size of the text. We need to adjust the font-size of the dummy element until the size of the text (as well as of the dummy element) should be confined to the size of the element (whose font-size you want to adjust). I made the code very clearly, hope you understand it better after reading the code:
//we just need 1 dummy element for the whole page.
var dummy = document.createElement('div');
dummy.className = 'dummy';
var inSingleLineMode, inMultilineMode;
//function used to adjust the font-size of the element
//so that the width is fixed (single-line mode) or both the width and height are
//fixed (multi-line mode), of course the text should be contained within
//the fixed width and height.
function adjustFontSize(element, singleLine){
if(!element.innerHTML) return;
var elementStyle = getComputedStyle(element);
dummy.style.font = elementStyle.font;
initMode(singleLine, function(){ dummy.style.width = elementStyle.width });
dummy.style.padding = elementStyle.padding;
dummy.style.boxSizing = elementStyle.boxSizing;
dummy.innerHTML = element.innerHTML;
document.body.appendChild(dummy);
var dummyStyle = getComputedStyle(dummy);
while(singleLine ? parseInt(dummyStyle.width) < parseInt(elementStyle.width) :
parseInt(dummyStyle.height) < parseInt(elementStyle.height)){
dummy.style.fontSize = parseFloat(dummyStyle.fontSize) + 1 + 'px';
dummyStyle = getComputedStyle(dummy);
}
while(singleLine ? parseInt(dummyStyle.width) > parseInt(elementStyle.width) :
parseInt(dummyStyle.height) > parseInt(elementStyle.height)){
dummy.style.fontSize = parseFloat(dummyStyle.fontSize) - 1 + 'px';
dummyStyle = getComputedStyle(dummy);
}
element.style.fontSize = dummyStyle.fontSize;
document.body.removeChild(dummy);
}
function initMode(singleLine, callback){
if(!dummy) return;
if(singleLine&&!inSingleLineMode) {
dummy.style.whiteSpace = 'nowrap';
dummy.style.width = 'auto';
dummy.style.display = "inline-block";
inSingleLineMode = true;
inMultiLineMode = false;
} else if(!singleLine&&!inMultilineMode) {
if(callback) callback();
dummy.style.whiteSpace = 'initial';
dummy.style.display = "block";
dummy.style.wordWrap = 'break-word';
inMultilineMode = true;
inSingleLineMode = false;
}
}
Demo.
In the demo, you can see that the first menu #menu1 is the Vietnamese word meaning Chrysanthemum while the second menu #menu2 is of course the English word Chrysanthemum. They have much different length, however both are supposed to have fixed width of 100px, hence the second menu #menu2 should have smaller font-size to fit the space.
You can use jQuery Text Fill like this.
Load up the plugin: <script src="jquery.textfill.js" ></script>
Put an id <input type="text" id="dyntext" value="e=mc²"></input>
Use the code to do magic. Preferably put this in <script> tags:
The end result will look something like this:
function update() {
var size = parseInt($('#maxsize').val(), 10);
if (!isNaN(size)) {
$('.dyntextval').html($('#dyntext').val());
$('.jtextfill').textfill({debug: true, maxFontPixels: size});
}
}
$(function () {
$('#maxsize').keyup(update);
$('#dyntext').keyup(update);
update()
});
This is not possible without Javascript. Using Javascript, you can use one of the many libraries, like FitText.
So you could use a Javascript library for this, but that would also mean that various labels have different font sizes.
I think the best approach would be to style the menu in such a way that it gracefully handles multi-line captions. That way, the length doesn't really matter much.
Because some language are 'longer' than others (for instance French labels are on avarage 1.5 to 2 times as long as English, it's a good idea to test your interface with one of those languages.
And for the font size, you could add a modifier on server side, for instace if you know the current language is French, you can add a class 'gui-captions-very-long' to the html tag and apply your CSS based on that class. That way, you can have a generic modifier which you can configure per language. I think that's a better solution than making all labels fit on a single line.
Keep in mind though, that smaller sizes are harder to read. You cannot just make the fonts half the size if the text is twice as long. You'll have to tune your design (or its implementation) to make longer texts possible.
I did look for that in the past then found an answer that really did the trick for me, but cannot remember were exactly... :(
But since it does answer the question using pure javascript and no plugins/libraries (did some optimisations since though), here it is! (with a working example):
// First we add a new function to the String prototype,
// this will be used to get the length of current string according
// to its font-family and font-size
String.prototype.textWidth = function(fontFamily, fontSize) {
var container = document.createElement('div');
container.style.visibility = 'hidden';
container.style.fontFamily = fontFamily;
container.style.fontSize = fontSize + 'px';
container.style.display = 'inline';
document.body.appendChild(container);
container.innerHTML = this;
var pxLength = container.offsetWidth;
container.parentNode.removeChild(container);
return pxLength;
};
// this is the function that will resize our text if it's too long
// classNameTarget (String) = the className of the element we need to resize e a
// tag or an id but you'll need to make modification to this function!
// maxWidth (int) = the max width (in px) of your final string
// fontFamily (String) = the family currently used by your string(wrong one might lead
// to wrong result!)
// fontSize (int) = the initial font-size of your string
var testWidth = function(classNameTarget, maxWidth, fontFamily, fontSize) {
maxWidth = maxWidth || 100;
// first we get all targets
var containers = document.getElementsByClassName(classNameTarget);
for (var i = 0; i < containers.length; i++) {
// for each of them we fetch their current length
var length = containers[i].innerHTML.textWidth(fontFamily, fontSize);
if (length > maxWidth){
// if the current length is bigger then we resize it by using a span styling with
// the new font-size
containers[i].innerHTML = "<span style=\"font-size:" + Math.floor(parseInt(fontSize) / (length / maxWidth)) + "px;\">" + containers[i].innerHTML + "</span>";
}
}
};
// we want our cell to have text no longer than 75px while having them starting at 50px
// font-size in arial
testWidth("firstname", 75, "arial", 50);
testWidth("lastname", 75, "arial", 50);
<table>
<thead>
<tr>
<th>firstname</th>
<th>lastname</th>
</tr>
</thead>
<tbody style="font-size: 50px; font-family: arial;">
<tr>
<td class="firstname">Bob</td>
<td class="lastname">Tomysonorubia</td>
</tr>
<tr>
<td class="firstname">John</td>
<td class="lastname">Doe</td>
</tr>
<tr>
<td class="firstname">François-Xavier</td>
<td class="lastname">De la nouvelle Orléan</td>
</tr>
</tbody>
</table>
the testWidth method might need some adjustements regarding your currents needs, perhaps you'ld like to look into querySelector or querySelectorAll to make it quite generic
I suggest an tiny example. On a pure JavaScript.
https://gist.github.com/dejurin/9bef02be6876e068ee276bee31cb3bcb
"use strict";
(function(w, d) {
var fit = d.getElementById("fit");
var wrap = d.getElementById("wrap");
fontFitResize(fit, wrap);
function fontFitResize(fit, wrap, step = 0.5) {
var currentSize;
while(fit.offsetWidth < wrap.offsetWidth) {
currentSize = parseFloat(w.getComputedStyle(wrap, null).getPropertyValue('font-size'));
wrap.style.fontSize = (currentSize - step) + 'px';
console.log(wrap.style.fontSize);
}
}
})(window, document);
.fit {
border: 1px solid #ff0000;
white-space:nowrap;
font-size:24px;
width:200px;
}
<div id="fit" class="fit">
<span id="wrap">Resize font depending on string length</span>
</div>

Resources