insert text at cursor position in a search field - google-chrome-extension

This is what I would like to do: copy some text in the clipboard. Place the cursor in a search field in the browser page, call the context menu of my extension which will bring a regular expression.
Apply the regex to clipboard content, put the result in the search field.
If I fetch the cplipboard content with:
navigator.clipboard.readText().then((clipText)=>
{
let val =applyRegex(clipText, data);
insertTextAtCursor(val);
}
InsertTextAtCursor below works, at least on google search page.
function insertTextAtCursor(text) {
log("insertTextAtCursor: " + text)
let el = document.activeElement;
let val = el.value;
let endIndex;
let range;
let doc = el.ownerDocument;
if (typeof el.selectionStart === 'number' &&
typeof el.selectionEnd === 'number') {
endIndex = el.selectionEnd;
el.value = val.slice(0, endIndex) + text + val.slice(endIndex);
el.selectionStart = el.selectionEnd = endIndex + text.length;
// && doc.selection.createRange)
} else if (doc.selection !== 'undefined' && doc.selection.createRange) {
el.focus();
range = doc.selection.createRange();
range.collapse(false);
range.text = text;
range.select();
//document.execCommand('insertText', false, text);
}
}//insertTextAtCursor
If I fetch the clipboard content with
function paste() {
var input = document.createElement('textarea');
//input.value = text;
//input.setAttribute('readonly', '');
input.style.position= 'absolute';
input.style.left= '-9999px';
document.body.appendChild(input);
input.select();
var result = document.execCommand('paste');
result = input.value;
log("paste: " + result);
document.body.removeChild(input);
return result;
}
doc.selection.createRange crash in InsertTextAtCursor, saying doc.selection is not defined.
I would prefer to stick with document.execCommand('paste'). So any help is welcome !
See https://sourceforge.net/p/skimtheclipboard-mv3/mercurial/ci/default/tree/
for the whole code.
Here is a test for a minimal example.
manifest.json
{
"background": {
"service_worker": "background.js",
"type": "module"
},
"content_scripts": [ {
"js": [ "contentok.js" ],
"matches": [ "\u003Call_urls>" ]
} ],
"description": "test",
"manifest_version": 3,
"name": "test",
"optional_permissions": [ ],
"permissions": ["contextMenus", "activeTab", "clipboardRead", "clipboardWrite" ],
"version": "0.0.1"
}
background.js
function buildContextMenu () {
let fcb =[ {fcb_context: "fcb_copy", title: "copy", context: ["selection", "link"]},
{fcb_context:"fcb_paste", context:["editable"], title:"paste"}];
for (let i=0; i< fcb.length; i++) {
let menu = fcb[i];
chrome.contextMenus.create({
title: menu.title,
id: menu.fcb_context,
contexts: menu.context,
});
} // for i
} //buildContextMenu
function log(msg, force=false) {
var debug = true;
if (force || debug){console.log(msg);}
}//Log
chrome.runtime.onInstalled.addListener( function () {
log("onInstalled called");
chrome.contextMenus.removeAll();
buildContextMenu();
}); //add listener
chrome.contextMenus.onClicked.addListener(function(info, tabs){
let data, sel, fcb_context;
let id = info.menuItemId;
log(" id: " + id ) ;
sel = info.selectionText;
fcb_context = info.parentMenuItemId;
( async () =>{
const [tab] = await chrome.tabs.query({active: true, currentWindow: true});
const response = await chrome.tabs.sendMessage( tab.id, {data: "go", context: fcb_context, sel: sel}, function(){} );
log(response);
})();
}); //onClickedadd
chrome.runtime.onStartup.addListener(function() {
log("onStartup called");
chrome.contextMenus.removeAll();
buildContextMenu();
}); //addListerner
contentok.js
chrome.runtime.onMessage.addListener(
function(request, sender) {
console.log("received in cs: " + JSON.stringify(request));
navigator.clipboard.readText().then((clipText)=>
{
let val = "Clipboard content : " + clipText;
insertTextAtCursor(val);
}
)
return true;
}
);
function insertTextAtCursor(text) {
console.log("insertTextAtCursor: " + text)
let el = document.activeElement;
let val = el.value;
let endIndex;
let range;
let doc = el.ownerDocument;
if (typeof el.selectionStart === 'number' &&
typeof el.selectionEnd === 'number') {
endIndex = el.selectionEnd;
el.value = val.slice(0, endIndex) + text + val.slice(endIndex);
el.selectionStart = el.selectionEnd = endIndex + text.length;
// && doc.selection.createRange)
} else if (doc.selection !== 'undefined' && doc.selection.createRange) {
el.focus();
range = doc.selection.createRange();
range.collapse(false);
range.text = text;
range.select();
// document.execCommand('insertText', false, text);
}
}//insertTextAtCursor
Once you have copied a string in the clipboard, you go to the google search page (or any page that have an editable field), place the cursor in that field, click on the paste menu of the context menu. This works as expected and inserts the clipboard content in the search field of the page.
Now you change the manifest to have the following content file used:
contentnotok.js which use the function paste()
chrome.runtime.onMessage.addListener(
function(request, sender) {
console.log("received in cs: " + JSON.stringify(request));
let val = paste();
insertTextAtCursor("Clipboard content: " + val);
return true;
}
);
function insertTextAtCursor(text) {
console.log("insertTextAtCursor: " + text)
let el = document.activeElement;
let val = el.value;
let endIndex;
let range;
let doc = el.ownerDocument;
if (typeof el.selectionStart === 'number' &&
typeof el.selectionEnd === 'number') {
endIndex = el.selectionEnd;
el.value = val.slice(0, endIndex) + text + val.slice(endIndex);
el.selectionStart = el.selectionEnd = endIndex + text.length;
// && doc.selection.createRange)
} else if (doc.selection !== 'undefined' && doc.selection.createRange) {
el.focus();
range = doc.selection.createRange();
range.collapse(false);
range.text = text;
range.select();
// document.execCommand('insertText', false, text);
}
}//insertTextAtCursor
function paste() {
var input = document.createElement('textarea');
//input.value = text;
//input.setAttribute('readonly', '');
input.style.position= 'absolute';
input.style.left= '-9999px';
document.body.appendChild(input);
input.select();
var result = document.execCommand('paste');
result = input.value;
console.log("paste: " + result);
document.body.removeChild(input);
return result;
}
If you inspect the page when calling the paste entry in the context menu, you have the Error in event handler: TypeError: Cannot read properties of undefined (reading 'createRange') in line 27 of the script.
As I said I would better use this paste function, which works looking at the log, but calling it seems to prevent insertTextAtCursor from working correctly.
If someone can give a correction ...
François

The textarea of the web page where the user calls the context menu loose the focus because of the paste function which create a new editable element.
So if I store it before calling paste and put the focus again on it after the calls return, insertTextAtCursor works.
In contentnotok.js :
chrome.runtime.onMessage.addListener(
function(request, sender) {
console.log("received in cs: " + JSON.stringify(request));
let field = document.activeElement;
let val = paste();
field.focus();
insertTextAtCursor("Clipboard content : " + val);
return true;
}
);
``

Related

Other product variant is disabled and I can not select it for 2 products (JS)

I am a coding beginner and I am building a store. My problem is that I have a product in different fabrics. Now I can only select one fabric type and the other is disabled and I can not select no matter what I do. Perfect would be if I select the fabric type, the associated products are displayed.
That what i mean
/*============================================================================
Dynamic variant availability
- To disable, set dynamicVariantsEnable to false in theme.liquid
==============================================================================*/
setCurrentVariantAvailability: function(variant) {
var valuesToEnable = {
option1: [],
option2: [],
option3: []
};
// Disable all options to start
this.disableVariantGroup($(selectors.formContainer, this.$container).find('.variant-input-wrap'));
// Combine all available variants
var availableVariants = this.variantsObject.filter(function(el) {
if (variant.id === el.id) {
return false;
}
// Option 1
if (variant.option2 === el.option2 && variant.option3 === el.option3) {
return true;
}
// Option 2
if (variant.option1 === el.option1 && variant.option3 === el.option3) {
return true;
}
// Option 3
if (variant.option1 === el.option1 && variant.option2 === el.option2) {
return true;
}
});
// IE11 can't handle shortform of {variant} so extra step is needed
var variantObject = {
variant: variant
};
availableVariants = Object.assign({}, variantObject, availableVariants);
// Loop through each available variant to gather variant values
for (var property in availableVariants) {
if (availableVariants.hasOwnProperty(property)) {
var item = availableVariants[property];
var option1 = item.option1;
var option2 = item.option2;
var option3 = item.option3;
if (option1) {
if (valuesToEnable.option1.indexOf(option1) === -1) {
valuesToEnable.option1.push(option1);
}
}
if (option2) {
if (valuesToEnable.option2.indexOf(option2) === -1) {
valuesToEnable.option2.push(option2);
}
}
if (option3) {
if (valuesToEnable.option3.indexOf(option3) === -1) {
valuesToEnable.option3.push(option3);
}
}
}
}
// Have values to enable, separated by option index
if (valuesToEnable.option1.length) {
this.enableVariantOptionByValue(valuesToEnable.option1, 'option1');
}
if (valuesToEnable.option2.length) {
this.enableVariantOptionByValue(valuesToEnable.option2, 'option2');
}
if (valuesToEnable.option3.length) {
this.enableVariantOptionByValue(valuesToEnable.option3, 'option3');
}
},
updateVariantAvailability: function(evt, value, index) {
if (value && index) {
var newVal = value;
var optionIndex = index;
} else {
var $el = $(evt.currentTarget);
var newVal = $el.val() ? $el.val() : evt.currentTarget.value;
var optionIndex = $el.data('index');
}
var variants = this.variantsObject.filter(function(el) {
return el[optionIndex] === newVal;
});
// Disable all buttons/dropdown options that aren't the current index
$(selectors.formContainer, this.$container).find('.variant-input-wrap').each(function(index, el) {
var $group = $(el);
var currentOptionIndex = $group.data('index');
if (currentOptionIndex !== optionIndex) {
// Disable all options as a starting point
this.disableVariantGroup($group);
// Loop through legit available options and enable
for (var i = 0; i < variants.length; i++) {
this.enableVariantOption($group, variants[i][currentOptionIndex]);
}
}
}.bind(this));
},
disableVariantGroup: function($group) {
if (this.settings.variantType === 'dropdown') {
$group.find('option').prop('disabled', true)
} else {
$group.find('input').prop('disabled', true);
$group.find('label').toggleClass('disabled', true);
}
},
enableVariantOptionByValue: function(array, index) {
var $group = $(selectors.formContainer, this.$container).find('.variant-input-wrap[data-index="'+ index +'"]');
for (var i = 0; i < array.length; i++) {
this.enableVariantOption($group, array[i]);
}
},
enableVariantOption: function($group, value) {
// Selecting by value so escape it
value = value.replace(/([ #;&,.+*~\':"!^$[\]()=>|\/#])/g,'\\$1');
if (this.settings.variantType === 'dropdown') {
$group.find('option[value="'+ value +'"]').prop('disabled', false);
} else {
var $buttonGroup = $group.find('.variant-input[data-value="'+ value +'"]');
$buttonGroup.find('input').prop('disabled', false);
$buttonGroup.find('label').toggleClass('disabled', false);
}
},
Have already tried various things, but not come to the desired result, even disabling the function ensures that everything is displayed and also clickable.
I hope you can help me.
Best Regards

overwrite previous lines on copy paste in REPL with ANSI escapes codes

I have Node.js REPL with syntax highlighting for Scheme language using ANSI escape codes. The problem is that when I copy-paste the code I need to overwrite previous text with new text that is highlighted. I'm using F ANSI escape to move the cursor up clear each line (K code) and write color text on top.
Here is my code:
function run_repl(err, rl) {
const dynamic = options.d || options.dynamic;
var code = '';
var multiline = false;
var resolve;
// we use promise loop to fix issue when copy paste list of S-Expression
var prev_eval = Promise.resolve();
if (process.stdin.isTTY) {
rl.prompt();
}
var prev_line;
boostrap(interp).then(function() {
rl.on('line', function(line) {
code += line + '\n';
const lines = code.split('\n');
const cols = process.stdout.columns;
// fix formatting for previous lines that was echo
// ReadLine will not handle those
if (terminal && lines.length > 0) {
var f = new Formatter(code);
code = f.format();
const stdout = scheme(code).split('\n').map((line, i) => {
var prefix;
if (i === 0) {
prefix = unify_prompt(prompt, continuePrompt);
} else {
prefix = unify_prompt(continuePrompt, prompt);
}
return '\x1b[K' + prefix + line;
}).join('\n');
let num = lines.length - 1;
const format = `\x1b[${num}F${stdout}`;
process.stdout.write(format);
}
try {
if (balanced_parenthesis(code)) {
// we need to clear the prompt because resume
// is adding the prompt that was present when pause was called
// https://github.com/nodejs/node/issues/11699
rl.setPrompt('');
rl.pause();
prev_eval = prev_eval.then(function() {
var result = run(code, interp, dynamic);
code = '';
return result;
}).then(function(result) {
if (process.stdin.isTTY) {
print(result);
if (newline) {
// readline doesn't work with not ended lines
// it ignore those, so we end them ourselves
process.stdout.write("\n");
newline = false;
}
if (multiline) {
multiline = false;
}
}
rl.setPrompt(prompt);
rl.prompt();
rl.resume();
}).catch(function() {
if (process.stdin.isTTY) {
if (multiline) {
multiline = false;
}
}
rl.setPrompt(prompt);
rl.prompt();
rl.resume();
});
} else {
multiline = true;
// write spaces for next line according to indent rules
var ind = indent(code, 2, prompt.length - continuePrompt.length);
rl.setPrompt(continuePrompt);
rl.prompt();
spaces = new Array(ind + 1).join(' ');
if (terminal) {
rl.write(spaces);
}
}
} catch (e) {
console.error(e.message);
console.error(e.stack);
code = '';
rl.setPrompt(prompt);
rl.prompt();
}
});
}).catch(function(e) {
log_error('Internal Error: boostrap filed');
log_error(e.message || e);
console.error('Internal Error: boostrap filed');
});
}
this is my main function, I also have hacked on top of readline that highlight text while the user is typing but it's cleared after you press enter that's why I need to update previous lines on each enter (copy/paste also works the same as enter key because it generates line event).
The only relevant code is this part:
rl.on('line', function(line) {
code += line + '\n';
const lines = code.split('\n');
const cols = process.stdout.columns;
// fix formatting for previous lines that was echo
// ReadLine will not handle those
if (terminal && lines.length > 2) {
const stdout = scheme(code).split('\n').map((line, i) => {
var prefix;
if (i === 0) {
prefix = unify_prompt(prompt, continuePrompt);
} else {
prefix = unify_prompt(continuePrompt, prompt);
}
return '\x1b[K' + prefix + line;
}).join('\n');
let num = lines.length - 1;
const format = `\x1b[${num}F${stdout}`;
process.stdout.write(format);
}
try {
if (balanced_parenthesis(code)) {
// ...
// when Scheme code is not finished else is executed.
} else {
multiline = true;
// write spaces for next line according to indent rules
var ind = indent(code, 2, prompt.length - continuePrompt.length);
rl.setPrompt(continuePrompt);
rl.prompt();
spaces = new Array(ind + 1).join(' ');
if (terminal) {
rl.write(spaces);
}
}
} catch (e) {
}
});
The code works fine unless original lines wrap into multiple lines, and it happens very often because you can't detect copy paste and the code is inserting spaces (auto-indent feature when user type code and press enter) so if the original pasted command already have indentation they are doubled.
I'm testing with this expression:
(define foo (lambda ()
(let ()
(define foo (lambda ()
(let ()
(define foo (lambda ()
(let ()
(define foo (lambda ()
(let ()
(define foo (lambda ()
(let ()
(display "x"))))))))))))))))
The problematic code is on GitHub https://github.com/jcubic/lips/blob/devel/bin/lips.js
The problem is how to clear what was already written in the terminal with ANSI escape codes.
EDIT:
This is my latest code that shows the same error when copy-pasting:
function run_repl(err, rl) {
const dynamic = options.d || options.dynamic;
var code = '';
var multiline = false;
var resolve;
// we use promise loop to fix issue when copy paste list of S-Expression
let prev_eval = Promise.resolve();
if (process.stdin.isTTY) {
rl.prompt();
}
let prev_line;
bootstrap(interp).then(function() {
rl.on('line', function(line) {
code += line;
const lines = code.split('\n');
const cols = process.stdout.columns;
// fix formatting for previous lines that was echo
// ReadLine will not handle those
if (terminal && lines.length > 2) {
var count = 0;
// correction when line wrapps in original line
// that will be overwritten
lines.map(line => {
if (line.length > cols) {
count += Math.ceil(line.length / cols) - 1;
}
});
var f = new Formatter(code);
code = f.format();
const stdout = scheme(code).split('\n').map((line, i) => {
var prefix;
if (i === 0) {
prefix = unify_prompt(prompt, continuePrompt);
} else {
prefix = unify_prompt(continuePrompt, prompt);
}
return '\x1b[K' + prefix + line;
}).join('\n');
let num = lines.length + count;
const format = `\x1b[${num}F${stdout}\n`;
process.stdout.write(format);
}
code += '\n';
try {
if (balanced_parenthesis(code)) {
// we need to clear the prompt because resume
// is adding the prompt that was present when pause was called
// https://github.com/nodejs/node/issues/11699
rl.setPrompt('');
rl.pause();
prev_eval = prev_eval.then(function() {
const result = run(code, interp, dynamic, null, options.t || options.trace);
code = '';
return result;
}).then(function(result) {
if (process.stdin.isTTY) {
print(result);
if (newline) {
// readline doesn't work with not ended lines
// it ignore those, so we end them ourselves
process.stdout.write("\n");
newline = false;
}
if (multiline) {
multiline = false;
}
}
rl.setPrompt(prompt);
rl.prompt();
rl.resume();
}).catch(function() {
if (process.stdin.isTTY) {
if (multiline) {
multiline = false;
}
}
rl.setPrompt(prompt);
rl.prompt();
rl.resume();
});
} else {
multiline = true;
const ind = indent(code, 2, prompt.length - continuePrompt.length);
rl.setPrompt(continuePrompt);
rl.prompt();
const spaces = new Array(ind + 1).join(' ');
if (terminal) {
rl.write(spaces);
}
}
} catch (e) {
console.error(e.message);
console.error(e.stack);
code = '';
rl.setPrompt(prompt);
rl.prompt();
}
});
}).catch(function(e) {
log_error('Internal Error: bootstrap filed');
log_error(e.message || e);
console.error('Internal Error: bootstrap filed');
});
}
if you want to see the working code look at the LIPS git repo.

chrome.browserAction, disabling Icon

I am attempting to disable or enable my chrome extension based on the matches of the content scripts, but I think there is a better way. Below is my code. Seems like what I am attempting to accomplish could be achieved by chrome.declarativeContent.onPageChanged but that does not seem to fire consistently.
const IsMatch = (tab, matches) => {
if (tab ==undefined && tab == null && tab.url == undefined){
return false;
}
for(var ctr = 0; ctr < matches.length ; ctr ++){
var match = matches[ctr].substring(0, matches[ctr].length - 1);
if (tab.url.startsWith(match) ) {
return true;
}
}
return false;
}
const updateIcon = tabId => {
chrome.browserAction.disable();
chrome.tabs.query({ currentWindow: true, active: true }, function (tabs) {
var contentScripts = chrome.runtime.getManifest().content_scripts;
for(var i = 0; i < contentScripts.length ; i ++){
var contentScript = contentScripts[i];
if ( IsMatch(tabs[0], contentScript.matches) ) {
chrome.browserAction.enable();
console.log('chrome.tabs.query');
console.log(tabs[0]);
return;
}
}
});
};
chrome.tabs.onUpdated.addListener(updateIcon);
After the changes recommended by #wOxxOm I am now in a race condition here is the updated code
browser.runtime.onMessage.addListener(function(request, sender, sendResponse) {
if (request.message === 'enableIcon') {
console.log(sender.tab.id + ' ' + request.message);
chrome.browserAction.setIcon({tabId: sender.tab.id, path: 'icon-16.png'});
browser.browserAction.enable();
}
return true;
});
browser.tabs.onUpdated.addListener(function(tabId) {
console.log(tabId + ' disableIcon');
chrome.browserAction.setIcon({tabId: tabId, path: 'icon-16-Disabled.png'});
chrome.browserAction.disable();
});
Here is the ouput:
- 6: backgroundIcon.js:31 39 disableIcon
- 1: backgroundIcon.js:22 39 enableIcon
- 2: backgroundIcon.js:31 39 disableIcon

Code inject via Content Script gets removed

I am creating a Chrome Extension that would draw an image above tag showing Flash content. I have written the content script in my "contentscript.js" file which does nothing but simply inserts another script file "backgroundscript.js" into page's body via a script tag.
The code works on few of sites having flash videos and shows the image right above the Flash Player window but on other sites it does not show up. Also on few it shows up for a fraction of a second and then gets disappeared.
Below is my manifest file:
{
"name": "My First Chrome Extension",
"version": "1.0.0.0",
"description": "Tag videos from websites with a single click!",
"icons": { "16":"application_16.png","48":"application_48.png","128":"application_128.png" },
"homepage_url": "http://www.myurl.com",
"page_action":{
"default_icon":{
"19":"application_19.png",
"38":"application_38.png"
},
"default_title":"VDownloader browser plugin"
},
"content_scripts": [ { "js": [ "contentscript.js" ], "matches": [ "http://*/*" ], "run_at" : "document_end", "all_frames": true} ],
"plugins": [ { "path": "myplugin.dll", "public": true } ],
"manifest_version":2,
"minimum_chrome_version":"17",
"offline_enabled":true,
"permissions":["tabs","http://*/*", "https://*/*","contextMenus"],
"web_accessible_resources":["application.png","backgroundscript.js"]
}
Also I checked by inserting "debugger;" into my script and monitoring code execution via console; the script gets added at least once into the page body. Even in the cases where no image button is shown.
Is this some kind of anit-XSS control implemented on the the more advanced sites not showing my button?
I would really appreciate your help as I have been trying to get over this from past couple of weeks without success :(
EDIT:
Please see the content script below:
var s = document.createElement('script');
s.src = chrome.extension.getURL("backgroundscript.js");
s.onload = function() {
this.parentNode.removeChild(this);
};
(document.head||document.documentElement).appendChild(s);
Target sites:
Does not show the button: http://www.metacafe.com
Shows the button for a moment: http://www.dailymotion.com
Shows the button correctly: http://www.myreviewalarm.com
Please take a look at backgroundscript.js content:
var vdDelayTimer;
var vdUpdateInterval;
var playerButtons;
embedPlugin();
function embedPlugin() {
var embed = document.createElement('embed');
embed.setAttribute('type', 'application/x-myplugin');
embed.setAttribute('id', 'myplugin');
embed.setAttribute('style', 'width:1px;height:1px;position:absolute;left:0px;top:0px;');
if(document.body!=null)
{
document.body.appendChild(embed);
}
testPlugin();
}
function testPlugin() {
var plugin = document.getElementById('vdPlugin');
if (plugin != null) {
playerButtons = [];
vdDelayTimer = setTimeout("appendButtons()", 2000);
}
else {
window.alert('Plugin does not exist!');
}
}
function updateButtons() {
for (i = 0; i < playerButtons.length; i += 2) {
updateButtonLocation(playerButtons[i], playerButtons[i + 1]);
}
}
function updateButtonLocation(player, button) {
var playerX = getX(player);
var playerY = getY(player);
var buttonHeight = 38;
var buttonWidth = 196;
var buttonX = playerX + player.offsetWidth - buttonWidth - 12;
var buttonY = playerY - buttonHeight + 1;
button.setAttribute('style', 'background-image: url(http://myurl.com/img/browser-download-button.png); width:' + buttonWidth + 'px;height:' + buttonHeight + 'px;position:absolute;left:' + buttonX + 'px;top:' + buttonY + 'px; cursor:pointer; cursor:hand;');
}
function appendButtons() {
debugger;
var objectTags = document.getElementsByTagName('object');
for (var i = 0; i < objectTags.length; i++) {
var objectTag = objectTags[i];
if ((objectTag.hasAttribute('classid') && objectTag.getAttribute('classid') == 'clsid:D27CDB6E-AE6D-11cf-96B8-444553540000')
|| (objectTag.hasAttribute('type') && objectTag.getAttribute('type') == 'application/x-shockwave-flash')) {
if (objectTag.offsetWidth >= 320 && objectTag.offsetHeight >= 240)
createButton(objectTag, getX(objectTag), getY(objectTag), objectTag.offsetWidth);
}
}
var embedTags = document.getElementsByTagName('embed');
for (var i = 0; i < embedTags.length; i++) {
try {
var embedTag = embedTags[i];
if (embedTag.hasAttribute('type') && embedTag.getAttribute('type') == 'application/x-shockwave-flash') {
if (embedTag.offsetWidth >= 320 && embedTag.offsetHeight >= 240)
createButton(embedTag, getX(embedTag), getY(embedTag), embedTag.offsetWidth);
}
}
catch (err) { }
}
var videoTags = document.getElementsByTagName('video');
for (var i = 0; i < videoTags.length; i++) {
try {
var videoTag = videoTags[i];
if (videoTag.hasAttribute('src')) {
if (videoTag.offsetWidth >= 320 && videoTag.offsetHeight >= 240)
createButton(videoTag, getX(videoTag), getY(videoTag), videoTag.offsetWidth);
}
}
catch (err) { }
}
vdUpdateInterval = setInterval("updateButtons();", 500);
}
function createButton(videoTag, playerX, playerY, playerWidth) {
debugger;
var buttonHeight = 38;
var buttonWidth = 196;
var buttonX = playerX + playerWidth - buttonWidth - 12;
var buttonY = playerY - buttonHeight + 1;
var vdPlugin = document.getElementById('vdPlugin');
var strLocation = window.location.toString();
// if (isSupported(strLocation)) {
var downloadButton = document.createElement('img');
downloadButton.setAttribute('title', 'Tag this video');
downloadButton.setAttribute('src', 'http://myurl.com/img/browser-download-button.png');
downloadButton.setAttribute('style', 'background-image: url(http://myurl.com/img/browser-download-button.png); width:' + buttonWidth + 'px;height:' + buttonHeight + 'px;position:absolute;left:' + buttonX + 'px;top:' + buttonY + 'px; cursor:pointer;cursor:hand;z-index:2147483647;');
downloadButton.setAttribute('onclick', 'javascript:document.getElementById(\'vdPlugin\').downloadNow(window.location.href);');
downloadButton.setAttribute('oncontextmenu', 'javascript:return false;');
document.body.appendChild(downloadButton);
playerButtons.push(videoTag, downloadButton);
// }
}
function getY(oElement) {
var iReturnValue = 0;
while (oElement != null) {
iReturnValue += oElement.offsetTop;
oElement = oElement.offsetParent;
}
return iReturnValue;
}
function getX(oElement) {
var iReturnValue = 0;
while (oElement != null) {
iReturnValue += oElement.offsetLeft;
oElement = oElement.offsetParent;
}
return iReturnValue;
}
function isSupported(url) {
var regLen = supportedSites.length;
var regEx;
var res = false;
try {
for (var i = 0; i < regLen && res == false; i++) {
regEx = new RegExp(supportedSites[i], "i");
res = regEx.test(url);
}
}
catch (ex) {
}
return res;
}

Knockout-2.2.0, subscribe get value before change AND new value

jsfiddle link: http://jsfiddle.net/T8ee7/
When I call Knockout's subscribe method is there a way I can get both the previous and new value? Right now, I can only call get these values separately.
I want to trigger some code if the old and new value are different.
I suppose I could do the following, but it can get messy...
(http://jsfiddle.net/MV3fN/)
var sv = sv || {};
sv.PagedRequest = function (pageNumber, pageSize) {
this.pageNumber = ko.observable(pageNumber || 1);
this.numberOfPages = ko.observable(1);
this.pageSize = ko.observable(pageSize || sv.DefaultPageSize);
};
var _pagedRequest = new sv.PagedRequest();
var oldValue;
_pagedRequest.pageNumber.subscribe(function (previousValue) {
console.log("old: " + previousValue);
oldValue = previousValue;
}, _pagedRequest, "beforeChange");
_pagedRequest.pageNumber.subscribe(function (newValue) {
console.log("new: " + newValue);
if (oldValue != newValue) {
console.log("value changed!");
}
});
_pagedRequest.pageNumber(10);
_pagedRequest.pageNumber(20);
​
I prefer using an observable extender.
http://jsfiddle.net/neonms92/xybGG/
Extender:
ko.extenders.withPrevious = function (target) {
// Define new properties for previous value and whether it's changed
target.previous = ko.observable();
target.changed = ko.computed(function () { return target() !== target.previous(); });
// Subscribe to observable to update previous, before change.
target.subscribe(function (v) {
target.previous(v);
}, null, 'beforeChange');
// Return modified observable
return target;
}
Example Usage:
// Define observable using 'withPrevious' extension
self.hours = ko.observable().extend({ withPrevious: 1 });
// Subscribe to observable like normal
self.hours.subscribe(function () {
if (!self.hours.changed()) return; // Cancel if value hasn't changed
print('Hours changed from ' + self.hours.previous() + ' to ' + self.hours());
});
This seems to work for me
ko.observable.fn.beforeAndAfterSubscribe = function (callback, target) {
var _oldValue;
this.subscribe(function (oldValue) {
_oldValue = oldValue;
}, null, 'beforeChange');
this.subscribe(function (newValue) {
callback.call(target, _oldValue, newValue);
});
};
See more at: http://ideone.com/NPpNcB#sthash.wJn57567.dpuf
http://jsfiddle.net/MV3fN/3/
var sv = sv || {};
sv.PagedRequest = function (pageNumber, pageSize) {
var self = this;
self.pageNumber = ko.observable(pageNumber || 1);
self.numberOfPages = ko.observable(1);
self.pageSize = ko.observable(pageSize || sv.DefaultPageSize);
self.pageNumber.subscribe(function (previousValue) {
console.log(previousValue);
console.log(self.pageNumber.arguments[0]);
if (previousValue != _pagedRequest.pageNumber.arguments[0]) {
console.log('value changed');
}
else {
//This won't get executed because KO doesn't
//call the function if the value doesn't change
console.log('not changed');
}
}, _pagedRequest, "beforeChange");
};
var _pagedRequest = new sv.PagedRequest();
_pagedRequest.pageNumber(10);
_pagedRequest.pageNumber(20);
_pagedRequest.pageNumber(20);
_pagedRequest.pageNumber(5);
I don't know if you're really supposed to use arguments[0], but it seems to work.
You could also set up your own method to accomplish this in a much cleaner way:
http://jsfiddle.net/PXKgr/2/
...
self.setPageNumber = function(page) {
console.log(self.pageNumber());
console.log(page);
if (self.pageNumber() != page) {
console.log('value changed');
}
else {
console.log('not changed');
}
self.pageNumber(page);
};
...
_pagedRequest.setPageNumber(10);
_pagedRequest.setPageNumber(20);
_pagedRequest.setPageNumber(20);
_pagedRequest.setPageNumber(5);

Resources