I'm fiddling around with vue for the first time and having troubles with getting v-bind:style="styleObject" getting to work properly. It works when styleObject only has one key/value-pair in it, but nothing comes when I have more than 1 key/value-pair.
When running console.log() the values comes out as they should.
My vue code:
<script>
import Vue from 'vue';
import ImageObject from './SkyCropImage.class';
export default Vue.component('sky-crop', {
props: {
src: String,
focalpoint: String,
mode: String,
round: String,
type: {
type: String,
default: 'img',
},
},
data() {
return {
image: new ImageObject(this.src),
srcString: '',
styleObject: { },
};
},
methods: {
anchorString(image) {
if (this.$el.firstChild.localName !== 'img') {
this.styleObject.backgroundPosition = `${image.anchor.x} ${image.anchor.y}`;
} else {
const pointX = (image.anchor.x.replace('%', '') * 1) / 100;
const pointY = (image.anchor.y.replace('%', '') * 1) / 100;
const differenceX = image.parent.width - image.calculatedInfo.width;
const differenceY = image.parent.height - image.calculatedInfo.height;
const anchorX = Math.min(0, differenceX * pointX);
const anchorY = Math.min(0, differenceY * pointY);
this.styleObject.transform = `translate(${anchorX}px, ${anchorY}px)`;
}
},
concatSrc(string) {
this.srcString = string;
if (this.type !== 'img') {
this.styleObject.backgroundImage = `url(${string})`;
}
},
},
created() {
this.image.mode = this.mode;
this.image.round = this.round;
this.image.anchor = {
x: this.focalpoint.split(',')[0],
y: this.focalpoint.split(',')[1],
};
},
mounted() {
this.image.setParentInfo(this.$el);
this.image.runCropJob();
this.anchorString(this.image);
this.concatSrc(this.image.outputUrl);
},
});
My template:
<div class="skyCrop-parent">
<img
class="skyCrop-element"
alt=""
v-if="type === 'img'"
v-bind:src="srcString"
v-bind:style="styleObject" />
// img result: <img alt="" src="https://source.unsplash.com/Ixp4YhCKZkI/700x394" class="skyCrop-element" style="transform: translate(-50px, 0px);">
<div
class="skyCrop-element"
v-bind:style="styleObject"
v-else>
</div>
//div result: <div class="skyCrop-element"></div>
</div>
How the component is called:
<sky-crop
src="https://source.unsplash.com/Ixp4YhCKZkI/1600x900"
focalpoint="50%,50%"
mode="width"
round="175"
type="div">
</sky-crop>
<sky-crop
src="https://source.unsplash.com/Ixp4YhCKZkI/1600x900"
focalpoint="50%,50%"
mode="width"
round="175">
</sky-crop>
The bug lies in the way Vue handles reactivity.
Since I tried to add key/value pair to styleObject like this:
this.styleObject.backgroundPosition = `${image.anchor.x} ${image.anchor.y}`;
Vue could not detect the change since the keys i tried to reference was not declare beforehand. The solution could be defining all future could be keys, which would work just fine. However using vm.$set() would be better since it handles creating the key and initiates the reactivity at the same time. In short this line (and the others which did the same):
this.styleObject.backgroundPosition = `${image.anchor.x} ${image.anchor.y}`;
Became this:
this.$set(this.styleObject, 'background-position', `${image.anchor.x} ${image.anchor.y}`);
Vue documentation about the reason for the change:
https://v2.vuejs.org/v2/guide/reactivity.html
Related
I created a custom photo carousel component in React (because external libraries were either too hard to work with or did not do the things that I wanted it to do), where you can swipe to the next/previous photo on mobile. Everything works fine on Android, but it's just IOS Safari.
The Problem
I have a page that maps out several carousels. The first carousel in the map works perfectly fine. Subsequent carousels will swipe correctly AFTER the first slide, but once it transitions to the second slide, the touch events stop firing, and it will not swipe. What I want is all the carousels like the first carousel. No error messages seen either. See video:
Code
Here is the custom component:
import { useState, useEffect } from 'react'
const Carousel = ({ children }) => {
const IMG_WIDTH = 400
const [currentIndex, setCurrentIndex] = useState(0)
const [lastTouch, setLastTouch] = useState(0)
const [movement, setMovement] = useState(0)
const [transitionDuration, setTransitionDuration] = useState('')
const [transitionTimeout, setTransitionTimeout] = useState(null)
const maxLength = children.length - 1,
maxMovement = maxLength * IMG_WIDTH
useEffect(() => {
return () => {
clearTimeout(transitionTimeout)
}
}, [])
const transitionTo = (index, duration) => {
setCurrentIndex(index)
setMovement(index * IMG_WIDTH)
setTransitionDuration(`${duration}s`)
setTransitionTimeout(
setTimeout(() => {
setTransitionDuration('0s')
}, duration * 100))
}
const handleMovementEnd = () => {
const endPosition = movement / IMG_WIDTH
const endPartial = endPosition % 1
const endingIndex = endPosition - endPartial
const deltaInteger = endingIndex - currentIndex
let nextIndex = endingIndex
if (deltaInteger >= 0) {
if (endPartial >= 0.1) {
nextIndex++
}
} else if (deltaInteger < 0) {
nextIndex = currentIndex - Math.abs(deltaInteger)
if (endPartial > 0.9) {
nextIndex++
}
}
transitionTo(nextIndex, Math.min(0.5, 1 - Math.abs(endPartial)))
}
const handleMovement = delta => {
clearTimeout(transitionTimeout)
const maxLength = children.length - 1
let nextMovement = movement + delta
if (nextMovement < 0) {
nextMovement = 0
}
if (nextMovement > maxLength * IMG_WIDTH) {
nextMovement = maxLength * IMG_WIDTH
}
setMovement(nextMovement)
setTransitionDuration('0s')
}
const handleTouchStart = event => {
setLastTouch(event.nativeEvent.touches[0].clientX)
}
const handleTouchMove = event => {
const delta = lastTouch - event.nativeEvent.touches[0].clientX
setLastTouch(event.nativeEvent.touches[0].clientX)
handleMovement(delta)
}
const handleTouchEnd = () => {
handleMovementEnd()
setLastTouch(0)
}
return (
<div
className="main"
style={{ width: IMG_WIDTH }}
onTouchStart={handleTouchStart}
onTouchMove={handleTouchMove}
onTouchEnd={handleTouchEnd}>
<div
className="swiper"
style={{
transform: `translateX(${movement * -1}px)`,
transitionDuration: transitionDuration
}}>
{children} // This is just <img /> tags
</div>
<div className="bullets">
{[...Array(children.length)].map((bullet, index) => (
<div key={`bullet-${index}`} className={`dot ${currentIndex === index && 'red-dot'}`} />
))}
</div>
</div>
)
}
export default Carousel
And here is the part of my code where I am using the custom component:
return (
<>
<Announcement />
<Header />
<section className="tiles-section">
<Title />
{Component} // This is just a component that simply maps out the Carousel, nothing special and no extra styling
</section>
...
</>
)
And CSS:
.main {
overflow: hidden;
position: relative;
touch-action: pan-y;
}
.swiper {
display: flex;
overflow-x: visible;
transition-property: transform;
will-change: transform;
}
What I know/tried
Removing some of the components above the Carousel (eg. Title, Announcement, etc.) makes part of the slides other than the first slide swipable. It can be half of the height of the slide or 1/3 of the height that is swipable, depending on how much I remove. This excludes the first carousel, that one still works perfectly fine
I've tried removing a bunch of CSS, didn't work
Tried adding event listeners to the 'main' div, maybe I did something wrong but swiping one carousel ends up swiping all the carousels
Thought it had something to do with the touch event handler functions I created in my custom carousel component, but it seems like they work fine, because after changing them to console.logs and manually translating the slides, the touch events are still not firing.
Update #1
I put the touch event handlers in a new div that wraps {child}. The second slide is swipeable now but the third slide is still a bust.
References
This is the tutorial link I followed to create the carousel for more context to the custom carousel component.
After days and hours, the solution is kind of odd. I'm guessing you need to set the target correctly to the element's div id. I put this in the useEffect, so the result looks like this:
useEffect(() => {
document.getElementById(id).addEventListener('touchstart', () => false, { passive: false })
return () => {
clearTimeout(transitionTimeout)
}
}, [])
NOTE that the id MUST be unique especially if creating a map of the same component like what I did. Otherwise, swiping one carousel will swipe all carousels.
I have one problem. addEventListener only works with the last element of the loop. I know what is the problem, but I can't figure it out. I get the JSON object from another function with the information. Later on the left side there should be clickable pictures. After clicking it I should get the same picture on the right side showed. Still it works only with the last one.
function myFunction(obj) {
var listItems = document.getElementsByClassName("newimg");
for (var i = 0; i < obj.length; i++) {
(function (i) {
document.getElementById("imgSmall").innerHTML += `<br></br><img id="${i}" class="newimg" src=${obj[i].download_url} >`;
let p = obj[i];
listItems[i].addEventListener('click', function() { makeithappen(p);},true);
}(i));
//obj[i].width,obj[i].height,obj[i].author,obj[i].download_url>
}
}
function makeithappen(k) {
document.getElementById("imgLarge").innerHTML = `<br class="text"> AUTHOR: ${k.author}, WIDTH: ${k.width}, HEIGHT: ${k.height}</br><img class="img2" src=${k.download_url} >`;
}
For quick fix.
Replace in your code
listItems[i].addEventListener('click', function() { makeithappen(p);},true);
with
listItems[i].onload = function() {
listItems[i].addEventListener('click', function () { makeithappen(p); }, true);
}
So when you got your listItems you weren't finished with the creation of more images. So new image means new list.
for (let i = 0; i < obj.length; i++) {
document.getElementById("imgSmall").innerHTML += `<br></br><img id="${i}" class="newimg" src=${obj[i].download_url}>`;
const listItems = document.getElementsByClassName("newimg");
listItems[i].addEventListener('click', function () { makeithappen(p); }, true);
}
function makeithappen(k) {
document.getElementById("imgLarge").innerHTML = `<br class="text"> AUTHOR: ${k.author}, WIDTH: ${k.width}, HEIGHT: ${k.height}</br><img class="img2" src=${k.download_url} >`;
}
Pleas do refactor <br></br> into something with css, margin or padding or whatever. This will then allow you to create the images with let div = document.createElement('img') and bind the event listener directly div.addEventlistener(...)
I'm trying to create a PDF using jsPDF and HTML2Canvas.
I have multiple DIVs to insert into the PDF.
If I try to put all DIVs into a container and render once then it only puts the first page height into the PDF.
Can't figure out how to render multiple divs and stick them in the same PDF so that it keeps going page by page.
JAVASCRIPT
function genPDF() {
html2canvas(document.getElementById("container"), {
onrendered: function (canvas) {
var img = canvas.toDataURL();
var doc = new jsPDF();
doc.addImage(img, 'PNG');
doc.addPage();
doc.save('test.pdf');
}
});
}
HTML
<div id="container">
<div class="divEl" id="div1">Hi <img src="img1.JPG"> </div>
<div class="divEl" id="div2">Why <img src="img2.PNG"> </div>
</div>
<button onClick="genPDF()"> Click Me </button>
Add each of your images separately.
You need to wait for all the html2canvas renderings are done and added to pdf and then save your final pdf.
One way to achieve this by using JQuery and array of promises, actual code would look like this:
function genPDF() {
var deferreds = [];
var doc = new jsPDF();
for (let i = 0; i < numberOfInnerDivs; i++) {
var deferred = $.Deferred();
deferreds.push(deferred.promise());
generateCanvas(i, doc, deferred);
}
$.when.apply($, deferreds).then(function () { // executes after adding all images
doc.save('test.pdf');
});
}
function generateCanvas(i, doc, deferred){
html2canvas(document.getElementById("div" + i), {
onrendered: function (canvas) {
var img = canvas.toDataURL();
doc.addImage(img, 'PNG');
doc.addPage();
deferred.resolve();
}
});
}
For me it works like that:
$('#cmd2').click(function () {
var len = 4; //$x(".//body/div/div").length
var pdf = new jsPDF('p', 'mm','a4');
var position = 0;
Hide
for (let i = 1;i <= len; i++){
html2canvas(document.querySelector('#pg'+i),
{dpi: 300, // Set to 300 DPI
scale: 1 // Adjusts your resolution
}).then(canvas => {
pdf.addImage(canvas.toDataURL("images/png", 1), 'PNG', 0,position, 210, 295);
if (i == len){
pdf.save('sample-file.pdf');
}else{
pdf.addPage();
}
});
}
});
You could try this, using the follwing javascript references in the HTML. Html2Canvas and jsPDF are quite picky with versions you use.
https://cdnjs.cloudflare.com/ajax/libs/html2canvas/0.4.1/html2canvas.js"
https://cdnjs.cloudflare.com/ajax/libs/jspdf/1.0.272/jspdf.debug.js"
var forPDF = document.querySelectorAll(".js-panel-pdf");
var len = forPDF.length;
var thisPDF = new jsPDF('p', 'mm', [240, 210]); //210mm wide and 297mm high
for (var i = 0; i < forPDF.length; i++) {
html2canvas(forPDF[i], {
onrendered: function(canvas) {
thisPDF.addImage(canvas.toDataURL("images/png", 1), 'PNG', 0, 0, 210, 295);
if (parseInt(i + 1) === len) {
thisPDF.save('sample-file.pdf');
} else {
thisPDF.addPage();
}
}
});
}
Stuart Smith - This is not working it does not allow to download the pdf
here is java script code
function generatePDF(){
var imgData;
var forPDF = document.querySelectorAll(".summary");
var len `enter code here`= forPDF.length;
var doc =new jsPDF('p','pt','a4');
for (var i = 0; i < forPDF.length; i++){
html2canvas(forPDF[i],{
useCORS:true,
onrendered:function(canvas){
imgData = canvas.toDataURL('image/jpg',1.0);
var doc =new jsPDF('p','pt','a4');
doc.addImage(imgData,'jpg',10,10,500,480);
if (parseInt(i + 1) === len) {
doc.save('sample-file.pdf');
} else {
doc.addPage();
}
}
});
}
}
I'm writing an extension that involving adding an item to Firefox's context menu, but it appends to the end of the menu and I couldn't find any pointers customizing item's position using Addon SDK (insertBefore/insertAfter), I know how this can be done using XUL, but I'm trying to do it using Addon SDK or some sort of Addon SDK/XUL combination
This is the code snippet related to context menu
main.js
var pageMod = require("sdk/page-mod");
var data = require("sdk/self").data;
var tabs = require("sdk/tabs");
var cm = require("sdk/context-menu");
pageMod.PageMod({
include: "*.youtube.com",
contentScriptFile: data.url("page.js"),
onAttach: function (worker) {
worker.port.emit('link', data.url('convertbutton.png'));
}});
cm.Item({
label: "Convert File",
image: data.url("bighdconverterlogo128png.png"),
context: [
cm.URLContext(["*.youtube.com"]),
cm.PageContext()
],
contentScriptFile: data.url("menu.js"),
onMessage: function(vUrl){
tabs.open(vUrl);
}
});
data/menu.js
self.on("click", function(){
self.postMessage('http://hdconverter.co/' + 'c.php?url=' + window.location.href);
});
Thanks
i dont know about sdk but for non-sdk addons its easy. but because you dont have the boiler plate setup its going to look long. add this code to your addon at the bottom:
var positionToInsertMenu = 0; //set the position you want it at here
var myLabelText = 'Convert File';
const {interfaces: Ci,utils: Cu} = Components;
Cu.import('resource://gre/modules/Services.jsm');
/*start - windowlistener*/
var windowListener = {
//DO NOT EDIT HERE
onOpenWindow: function (aXULWindow) {
// Wait for the window to finish loading
let aDOMWindow = aXULWindow.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindowInternal || Ci.nsIDOMWindow);
aDOMWindow.addEventListener("load", function () {
aDOMWindow.removeEventListener("load", arguments.callee, false);
windowListener.loadIntoWindow(aDOMWindow, aXULWindow);
}, false);
},
onCloseWindow: function (aXULWindow) {},
onWindowTitleChange: function (aXULWindow, aNewTitle) {},
register: function () {
// Load into any existing windows
let XULWindows = Services.wm.getXULWindowEnumerator(null);
while (XULWindows.hasMoreElements()) {
let aXULWindow = XULWindows.getNext();
let aDOMWindow = aXULWindow.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindowInternal || Ci.nsIDOMWindow);
windowListener.loadIntoWindow(aDOMWindow, aXULWindow);
}
// Listen to new windows
Services.wm.addListener(windowListener);
},
unregister: function () {
// Unload from any existing windows
let XULWindows = Services.wm.getXULWindowEnumerator(null);
while (XULWindows.hasMoreElements()) {
let aXULWindow = XULWindows.getNext();
let aDOMWindow = aXULWindow.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindowInternal || Ci.nsIDOMWindow);
windowListener.unloadFromWindow(aDOMWindow, aXULWindow);
}
//Stop listening so future added windows dont get this attached
Services.wm.removeListener(windowListener);
},
//END - DO NOT EDIT HERE
loadIntoWindow: function (aDOMWindow, aXULWindow) {
if (!aDOMWindow) {
return;
}
var contentAreaContextMenu = aDOMWindow.document.getElementById('contentAreaContextMenu');
var myMenuItem;
if (contentAreaContextMenu) {
var menuItems = contentAreaContextMenu.querySelector('menuitem');
[].forEach.call(menuItems, function(item) {
if (item.getAttribute('label') == myLabelText) {
myMenuItem = item;
}
});
contentAreaContextMenu.removeChild(myMenuItem);
if (contentAreaContextMenu.childNodes.length >= positionToInsertMenu) { //position is greater then number of childNodes so append to end
contentAreaContextMenu.appendChild(myMenuItem);
} else {
contentAreaContextMenu.insertBefore(myMenuItem, contentAreaContextMenu.childNodes[thePosition]);
}
}
},
unloadFromWindow: function (aDOMWindow, aXULWindow) {
if (!aDOMWindow) {
return;
}
var myMenuItem = aDOMWindow.document.getElementById('myMenuItem');
if (myMenuItem) {
myMenuItem.parentNode.removeChild(myMenuItem);
}
}
};
windowListener.register();
on unload of your addon add this:
windowListener.unregister();
i copied pasted from a template and modded it real fast. for position to be accurate you probably have to consider which menuitems are hidden and which are not
I am building an advanced search UI similar to the TFS query builder web interface. Using knockout for the client side implementation and have everything more or less working except the final validation to make certain required items are basically selected. It sort-of works as far as giving me a validation error if I select an item and then de-select the item. Which is fine, but I would like to have the form validate when hitting the search button.
I am pretty sure I need to make use of the ko.validatedobservable method, I'm just not sure exactly how. Anyway, I have a fiddle to look at: http://jsfiddle.net/sstolp/uXBSA/ if anyone has the time or inclination to help me out. I would deeply appreciate it.
Thank you for your time.
scvm.SearchLine = function () {
var self = this;
self.selectedField = ko.observable().extend({ required: true });
self.selectedOperator = ko.observable().extend({ required: true });
self.firstdate = ko.observable(new Date());
self.lastdate = ko.observable(new Date());
self.thedate = ko.observable(new Date());
return self;};
scvm.Criteria = function () {
var self = this,
lines = ko.observableArray([]),
// Put one line in by default
loadInitialData = function () {
lines.push(new scvm.SearchLine());
},
rowcount = ko.computed(function () {
return lines().length;
}),
// Operations
addLine = function () {
lines.push(new scvm.SearchLine());
},
removeLine = function (line) {
lines.remove(line);
},
search = function () {
var data = $.map(lines(), function (line) {
return line.selectedField() ? {
selectedField: line.selectedField().searchfield,
selectedOperator: line.selectedOperator().name,
} : undefined
});
alert("Send to server: " + JSON.stringify(data));
},
clear = function () {
lines.removeAll();
};
return {
lines: lines,
loadInitialData: loadInitialData,
rowcount: rowcount,
addLine: addLine,
removeLine: removeLine,
search: search,
clear: clear
};
}();
Yes, all your SearchLine objects must be wrapped into ko.validatedObservable. Also you should implement computed property which will check isValid() for each criteria line and return global validity flag.
scvm.SearchLine = function () {
var self = this;
self.selectedField = ko.observable().extend({ required: true });
self.selectedOperator = ko.observable().extend({ required: true });
self.firstdate = ko.observable(new Date());
self.lastdate = ko.observable(new Date());
self.thedate = ko.observable(new Date());
return ko.validatedObservable(self);
};
scvm.Criteria = function () {
// ...
return {
lines: lines,
loadInitialData: loadInitialData,
rowcount: rowcount,
addLine: addLine,
removeLine: removeLine,
search: search,
clear: clear,
// new property that indicates validity of all lines
linesValid: ko.computed(function(){
var items = lines();
for (var i = 0, l = items.length; i < l; i++)
if (!items[i].isValid()) return false;
return true;
})
};
}();
This new property can be used in enable binding of you "Search" button:
<input type="button"
data-bind="enable: linesValid, click: search"
title="Clicking this button will run a search."
value="Search" />
I've modified your fiddle. Take a look: http://jsfiddle.net/ostgals/uXBSA/8/
Update:
Also we should slightly modify Criteria.search method, since our line array contains observables rather than objects:
//...
search = function () {
var data = $.map(lines(), function (line) {
line = ko.utils.unwrapObservable(line);
return line.selectedField() ? {
selectedField: line.selectedField().searchfield,
selectedOperator: line.selectedOperator().name,
} : undefined
});
alert("Send to server: " + JSON.stringify(data));
},
//...