How to make a web component return a Proxy (and be extended) - class

The goal is to have a base class A extending HTMLElement that customizes getters and setters. Then class B would extend class A and do stuff.
The way to do this is by wrapping class A with a proxy (not the instance, but the class) so B can extend A.
I tried to return a Proxy in the constructor, but I get custom element constructors must call super() first and must not return a different object
<!DOCTYPE html>
<body>
<script>
window.onerror = function (error, url, line) {
document.getElementById('error').innerHTML = document.getElementById('error').innerHTML + '<br/>' + error;
};
</script>
<div id="error">Console errors here:<br /></div>
<my-b-element></my-b-element>
<script type="module">
class A extends HTMLElement {
constructor() {
super();
return new Proxy(this, {
get(target, name, receiver) {
let rv = Reflect.get(target, name, receiver);
console.log(`get ${name} = ${rv}`);
// do something with rv
return rv;
},
set(target, name, value, receiver) {
if (!Reflect.has(target, name)) {
console.log(`Setting non-existent property '${name}', initial value: ${value}`);
}
return Reflect.set(target, name, value, receiver);
}
});
}
}
class B extends A {
constructor() {
super();
}
}
customElements.define("my-b-element", B);
document.querySelector('my-b-element').nonExistentProperty = 'value1';
</script>
</body>
</html>

In case it helps anyone, here's how it's done without any proxy.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<script>
class Reactive extends HTMLElement {
#data = {};
connectedCallback() {
this.propertyObserver();
}
propertyObserver() {
const properties = Object.getOwnPropertyDescriptors(this);
// defines the new object properties including the getters and setters
for (let key in properties) {
const descriptor = properties[key];
this.#data[key] = descriptor.value;
descriptor.get = () => this.#data[key];
descriptor.set = (value) => {
const result = this.trap(key, value);
this.#data[key] = typeof result === 'undefined' ? value : result;
}
delete descriptor.value;
delete descriptor.writable;
}
Object.defineProperties(this, properties);
}
trap() {
// placeholder in case the child doesn't implement it
}
}
class Child extends Reactive {
a = 1;
b = 2;
constructor () {
super();
}
connectedCallback() {
super.connectedCallback();
}
trap(key, value) {
// You can return a value to override the value that is set
console.log(`LOG new ${key}: ${value} old: ${this[key]}`);
}
}
customElements.define("x-element", Child);
</script>
</head>
<body>
<x-element></x-element>
<script>
document.querySelector('x-element').a = 20;
</script>
</body>
</html>

Related

How to invoke a class method in google app script from client side?

How to invoke a class method in google app script from client side ?
//client side
function myClientSideFun() {
google.script.run.withSuccessHandler(onSuccess).myClass.myClassMethod()
function onSucces(msg) { console.log(msg) }
}
//server side
class MyClass {
myClassMethod() { return "myMsg" }
}
let myClass = new MyClass()
Unless you export the class method in a different top level function, it is not possible to directly call class methods from the client. Classes are just syntactic sugars around existing objects. The documentation on Private functions clearly says that obj.objectMethod() isn't callable from the client.
As a simple example of using an object method.
HTML_Test
<!DOCTYPE html>
<html>
<head>
<base target="_top">
</head>
<body>
<input id="testParam" type="text">
<script>
(function () {
google.script.run.withSuccessHandler(
function(param) {
document.getElementById("testParam").value = param;
}
).getTestParam();
})();
</script>
</body>
</html>
Code.gs
class TestObject {
constructor(param) {
this.param = param;
}
get getParam() { return this.param; }
}
var obj = new TestObject("hello");
function getTestParam() {
return obj.getParam;
}
Options 2
To build on what #Bede noted there are many ways to use server side objects.
HTML_Test
<!DOCTYPE html>
<html>
<head>
<base target="_top">
</head>
<body>
<input id="testParam" type="text">
<script>
(function () {
google.script.run.withSuccessHandler(
function(param) {
document.getElementById("testParam").value = param;
}
).getTestParam({name: "getParam2"});
})();
</script>
</body>
</html>
Code.gs
class TestObject {
constructor(param1,param2) {
this.param1 = param1;
this.param2 = param2;
}
getParam1() { return this.param1 }
getParam2() { return this.param2 }
}
var obj = new TestObject("hello","goodbye");
var getTestParam = param => { return obj[param.name](); }
Many thanks for the example and for the answer, based on that it seems I am figuring out another way how could I encapsulate functions on server side - actually with an object literal:
const appFunctionLibrary = {
appFunctions: {
"fun1": function (arg1) {
return arg1
},
"fun2": function (arg1, arg2) {
return [arg1, arg2]
},
"fun3": function () {
return "fun without param"
}
}
}
const main = requestedFunction =>
appFunctionLibrary.appFunctions[requestedFunction.name]
(requestedFunction.arguments)
Calling from client side /1 way: also create an "object" in client script and call ../:
<!DOCTYPE html>
<html>
<head>
<base target="_top">
</head>
<body>
<button type="button" onclick="clientObject.clientFun.call(clientObject)">Test</button>
<script>
function ClientObj() {
this.clientFun = function(){
let arg1 = 'hello'
let arg2 = 'from client'
google.script.run.withSuccessHandler(function onSuccess(msg) {
console.log(msg)
}).main({name:'fun2', arguments: `${arg1}, ${arg2}`}) // .main({name:'fun3', arguments: ""}) .main({name:'fun1', arguments: `${arg1}`})
}
}
clientObject = new ClientObj()
</script>
</body>
</html>

Show HTML in window with OK button in VSCode extension

I need to insert text after the cursor if the user agrees. I can't find anything in the documentation for my task.
Mission of my extension:
user writes code
then he calls the extension
it sends all code to a server
returns and shows some code in an additional window with an "OK" button
pressing the "OK" button inserts the server's response after the cursor
I have a problem with point 4. I can't find a method to show the additional window with an "OK" button.
All code from extension.js:
function activate(context) {
console.log('"showText" active');
const commandId = 'extension.showText'
let disposable = vscode.commands.registerCommand(commandId, function () {
const text = editor.document.getText()
let options = {
method: 'GET',
uri: 'http://example.com:8081/',
body: {
text: text
},
json: true
}
rp(options)
.then(function (respString) {
console.log(respString);
// what should I call here?
// vscode.window.showInformationMessage(respString);
})
.catch(function(err) {
console.log(err);
});
});
context.subscriptions.push(disposable);
}
exports.activate = activate;
function deactivate() {
console.log('showText disactive');
}
module.exports = {
activate,
deactivate
}
It is not duplicate of How to handle click event in Visual Studio Code message box? because I need HTML page. And this decision just shows text message with confim button.
My decision is:
webview-sample
Webview API
My example:
// The module 'vscode' contains the VS Code extensibility API
// Import the module and reference it with the alias vscode in your code below
const vscode = require('vscode');
const workspace = vscode.workspace;
const window = vscode.window;
const rp = require("request-promise");
// this method is called when your extension is activated
// your extension is activated the very first time the command is executed
/**
* #param {vscode.ExtensionContext} context
*/
function activate(context) {
// Use the console to output diagnostic information (console.log) and errors (console.error)
// This line of code will only be executed once when your extension is activated
console.log('Extension activated');
// The command has been defined in the package.json file
// Now provide the implementation of the command with registerCommand
// The commandId parameter must match the command field in package.json
const commandId = 'extension.showText'
let disposable = vscode.commands.registerCommand(commandId, function () {
const editor = window.activeTextEditor;
const text = editor.document.getText();
console.log('For editor "'+ editor._id +'"');
let options = {
method: 'POST',
uri: URL,
body: {
text: text
},
json: true
};
rp(options)
.then(function (res) { // res
const panel = window.createWebviewPanel(
'type_id', // Identifies the type of the webview. Used internally
'Title', // Title of the panel displayed to the user
vscode.ViewColumn.Two, // Editor column to show the new webview panel in.
{
// Enable scripts in the webview
enableScripts: true
} // Webview options. More on these later.
);
panel.webview.html = res;
// Handle messages from the webview
// закрывать когда выбираешь другое окошко
window.onDidChangeActiveTextEditor(
ev => {
// console.log(ev._id, editor._id, editor);
ev && ev._id && ev._id != editor._id && panel.dispose();
}
)
// закрывать когда закрываешь окно
workspace.onDidCloseTextDocument(
textDocument => {
console.log("closed => " + textDocument.isClosed)
panel.dispose();
},
null,
context.subscriptions
);
// любая клавиша кроме enter - фильтр по префиксу
// если enter - поиск подсказок
workspace.onDidChangeTextDocument(
ev => {
console.log(ev);
if (ev && ev.contentChanges && ev.contentChanges.length
&& (ev.contentChanges[0].text || ev.contentChanges[0].rangeLength)) {
// do smth
} else {
console.error('No changes detected. But it must be.', ev);
}
},
null,
context.subscriptions
);
panel.webview.onDidReceiveMessage(
message => {
switch (message.command) {
case 'use':
console.log('use');
editor.edit(edit => {
let pos = new vscode.Position(editor.selection.start.line,
editor.selection.start.character)
edit.insert(pos, message.text);
panel.dispose()
});
return;
case 'hide':
panel.dispose()
console.log('hide');
return;
}
},
undefined,
context.subscriptions
);
panel.onDidDispose(
() => {
console.log('disposed');
},
null,
context.subscriptions
)
})
.catch(function(err) {
console.log(err);
});
});
context.subscriptions.push(disposable);
}
exports.activate = activate;
// this method is called when your extension is deactivated
function deactivate() {
console.log('Extension disactivated');
}
module.exports = {
activate,
deactivate
}
The example of res:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Title</title>
<script src="https://code.jquery.com/jquery-3.3.1.slim.min.js" integrity="sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4YfRvH+8abtTE1Pi6jizo" crossorigin="anonymous"></script>
</head>
<body>
<p id="text">Text</p>
<button onclick="useAdvise()">Use</button>
<button onclick="hideAdvise()">Hide</button>
<script>
const vscode = acquireVsCodeApi();
function useAdvise(){
let text_ = $(document).find("#text").text();
vscode.postMessage({command: 'use',text: text_})
}
function hideAdvise(){
vscode.postMessage({command: 'hide'})
}
</script>
</body>
</html>

SharePoint bulk update of multiple list items

I am currently working with binding SharePoint list items using JQuery datatables and rest API with the following code from Microsoft samples. I want to extend the sample to include multiple selection of row items with check boxes so that I can then another method to update the selected items. Please let me if this is possible with any guidance
<script src="https://code.jquery.com/jquery-1.12.4.js"></script>
<script src="https://cdn.datatables.net/1.10.15/js/jquery.dataTables.js"></script>
<script src="https://momentjs.com/downloads/moment.min.js"></script>
<link rel="stylesheet" type="text/css" href="https://cdn.datatables.net/1.10.15/css/jquery.dataTables.min.css" />
<table id="requests" class="display" cellspacing="0" width="100%">
<thead>
<tr>
<th>ID</th>
<th>Business unit</th>
<th>Category</th>
<th>Status</th>
<th>Due date</th>
<th>Assigned to</th>
</tr>
</thead>
</table>
<script>
// UMD
(function(factory) {
"use strict";
if (typeof define === 'function' && define.amd) {
// AMD
define(['jquery'], function ($) {
return factory( $, window, document );
});
}
else if (typeof exports === 'object') {
// CommonJS
module.exports = function (root, $) {
if (!root) {
root = window;
}
if (!$) {
$ = typeof window !== 'undefined' ?
require('jquery') :
require('jquery')( root );
}
return factory($, root, root.document);
};
}
else {
// Browser
factory(jQuery, window, document);
}
}
(function($, window, document) {
$.fn.dataTable.render.moment = function (from, to, locale) {
// Argument shifting
if (arguments.length === 1) {
locale = 'en';
to = from;
from = 'YYYY-MM-DD';
}
else if (arguments.length === 2) {
locale = 'en';
}
return function (d, type, row) {
var m = window.moment(d, from, locale, true);
// Order and type get a number value from Moment, everything else
// sees the rendered value
return m.format(type === 'sort' || type === 'type' ? 'x' : to);
};
};
}));
</script>
<script>
$(document).ready(function() {
$('#requests').DataTable({
'ajax': {
'url': "../_api/web/lists/getbytitle('IT Requests')/items?$select=ID,BusinessUnit,Category,Status,DueDate,AssignedTo/Title&$expand=AssignedTo/Title",
'headers': { 'Accept': 'application/json;odata=nometadata' },
'dataSrc': function(data) {
return data.value.map(function(item) {
return [
item.ID,
item.BusinessUnit,
item.Category,
item.Status,
new Date(item.DueDate),
item.AssignedTo.Title
];
});
}
},
columnDefs: [{
targets: 4,
render: $.fn.dataTable.render.moment('YYYY/MM/DD')
}]
});
});
</script>

Ionic Local storage put information into array

Here is the code
Controller.js
$scope.addFavorite = function (index) {
$scope.temp = {
id: index
};
$scope.dish = $localStorage.getObject('favorites', '{}');
console.log($localStorage.get('favorites'));
$localStorage.storeObject('favorites', JSON.stringify($scope.temp));
var favorites = $localStorage.getObject('favorites');
console.log(favorites);
favoriteFactory.addToFavorites(index);
$ionicListDelegate.closeOptionButtons();
}
Service.js
.factory('favoriteFactory', ['$resource', 'baseURL', function ($resource, baseURL) {
var favFac = {};
var favorites = [];
favFac.addToFavorites = function (index) {
for (var i = 0; i < favorites.length; i++) {
if (favorites[i].id == index)
return;
}
favorites.push({id: index});
};
favFac.deleteFromFavorites = function (index) {
for (var i = 0; i < favorites.length; i++) {
if (favorites[i].id == index) {
favorites.splice(i, 1);
}
}
}
favFac.getFavorites = function () {
return favorites;
};
return favFac;
}])
.factory('$localStorage', ['$window', function($window) {
return {
store: function(key, value) {
$window.localStorage[key] = value;
},
get: function(key, defaultValue) {
return $window.localStorage[key] || defaultValue;
},
storeObject: function(key, value) {
$window.localStorage[key] = JSON.stringify(value);
},
getObject: function(key,defaultValue) {
return JSON.parse($window.localStorage[key] || defaultValue);
}
}
}])
I want to make a Favorites function, and I want to put every item's ID that marked into an array.
But, it couldn't expand the array and only change the value.
Did I make something wrong on here? Or maybe I put a wrong method on here?
Thank you in advance!
I just create logic for storing object, you have to made logic for remove object from localstorage.
<!DOCTYPE html>
<html ng-app="plunker">
<head>
<meta charset="utf-8" />
<title>AngularJS</title>
<link rel="stylesheet" type="text/css" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css">
<script>document.write('<base href="' + document.location + '" />');</script>
<script data-require="angular.js#1.4.x" src="https://code.angularjs.org/1.4.9/angular.js" data-semver="1.4.9"></script>
</head>
<body ng-controller="MainCtrl">
<div ng-repeat="item in items">
{{item.item_name}}
<button ng-click="addFavorite(item.id)">Add to Favorite</button>
<br><hr>
</div>
</body>
</html>
<script type="text/javascript">
var app = angular.module('plunker', []);
app.controller('MainCtrl', function($scope,$http)
{
$scope.items = [
{id:1,item_name:'Apple'},
{id:2,item_name:'Banana'},
{id:3,item_name:'Grapes'},
]
$scope.addFavorite = function (index)
{
if(localStorage.getItem('favorites')!=undefined)
{
var old_favorite = JSON.parse(localStorage.getItem('favorites'));
var obj = {index:index};
old_favorite.push(obj);
localStorage.setItem('favorites',JSON.stringify(old_favorite));
}
else
{
var obj = [{index:index}];
localStorage.setItem('favorites',JSON.stringify(obj));
}
}
});
</script>

Dart web app not accessing MongoDB

I am trying to write a simple single-page web app in Dart using the "observatory" package. What it should do is display all the data in the "Name" collection, retrieve the user's input, add lastly that input to both the on-screen list and the collection. The input gets added to the unordered list, but that's all that happens. I'm not sure what I'm doing wrong. Here is my Dart code:
import 'dart:html';
import 'package:objectory/objectory_browser.dart';
class Name extends PersistentObject {
String get collectionName => "Name";
String get first => getProperty("first");
set first(String value) => setProperty("first", value);
String get last => getProperty("last");
set last(String value) => setProperty("last", value);
}
const uri = 'mongodb://localhost/dosdart1';
registerClasses() {
objectory.registerClass(Name, () => new Name(), () => new List<Name>());
}
void main() {
objectory = new ObjectoryWebsocketBrowserImpl(uri, registerClasses, false);
loadNames();
querySelector('#output').text = 'Your Dart app is running.';
var btn = querySelector('#btn');
btn.onClick.listen(addName);
}
Future display(name) {
var completer = new Completer();
name.fetchLinks().then((__) {
var ul = querySelector('#names');
var li = new LIElement()
..appendText("${name.first} ${name.last}");
ul.append(li);
completer.complete(true);
});
return completer.future;
}
loadNames() {
objectory.initDomainModel().then((_) {
return objectory[Name].find();
}).then((names){
return Future.wait(names.map((name) => display(name)));
}).then((_) {
objectory.close();
});
}
addName(MouseEvent e) {
var name = new Name();
InputElement firstElement = querySelector('#first');
InputElement lastElement = querySelector('#last');
name.first = firstElement.value;
name.last = lastElement.value;
objectory.initDomainModel().then((_){
name.save();
return;
}).then((_) {
objectory.close();
});
display(name);
}
And here is my web page:
<!DOCTYPE html>
<!--
Copyright (c) 2015, <your name>. All rights reserved. Use of this source code
is governed by a BSD-style license that can be found in the LICENSE file.
-->
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="scaffolded-by" content="https://github.com/google/stagehand">
<title>hybrid_dart</title>
<link rel="stylesheet" href="styles.css">
<script async src="main.dart" type="application/dart"></script>
<script async src="packages/browser/dart.js"></script>
</head>
<body>
<div id="output"></div>
<p><label for="first">First name:</label><input type="text" id="first" name="first"></p>
<p><label for="last">Last name:</label><input type="text" name="last" id="last"></p>
<p><button id="btn" name="btn" title="Insert">Insert</button></p>
<ul id="names">
</ul>
</body>
</html>
Does anybody have any ideas?
You need to chain futures to ensure they are executed in order. I don't see for every call if it returns a Future or a sync value therefore I might miss here and there (I added /* await */ where in doubt).
The new async / await feature makes it much easier to work with async code therefore I changed all your then(...) to async/await.
main() async {
objectory = new ObjectoryWebsocketBrowserImpl(uri, registerClasses, false);
await loadNames();
querySelector('#output').text = 'Your Dart app is running.';
var btn = querySelector('#btn');
btn.onClick.listen(addName);
}
Future display(name) async {
await fetchLinks();
var ul = querySelector('#names');
var li = new LIElement()
..appendText("${name.first} ${name.last}");
ul.append(li);
}
Future loadNames() async {
await objectory.initDomainModel();
var names = await objectory[Name].find();
await Future.wait(names.map((name) => display(name)));
/* await */ objectory.close();
}
Future addName(MouseEvent e) async {
var name = new Name();
InputElement firstElement = querySelector('#first');
InputElement lastElement = querySelector('#last');
name.first = firstElement.value;
name.last = lastElement.value;
await objectory.initDomainModel();
name.save();
/* await */ objectory.close();
await display(name);
}
Without async/await it should be like
void main() {
objectory = new ObjectoryWebsocketBrowserImpl(uri, registerClasses, false);
loadNames().then((_) {
querySelector('#output').text = 'Your Dart app is running.';
var btn = querySelector('#btn');
btn.onClick.listen(addName);
});
}
Future display(name) {
return name.fetchLinks().then((__) {
var ul = querySelector('#names');
var li = new LIElement()
..appendText("${name.first} ${name.last}");
ul.append(li);
});
}
Future loadNames() {
return objectory.initDomainModel().then((_) {
return objectory[Name].find();
}).then((names){
return Future.wait(names.map((name) => display(name)));
}).then((_) {
/* return */ objectory.close();
});
}
Future addName(MouseEvent e) {
var name = new Name();
InputElement firstElement = querySelector('#first');
InputElement lastElement = querySelector('#last');
name.first = firstElement.value;
name.last = lastElement.value;
return objectory.initDomainModel().then((_){
name.save();
return;
// if "name.save();" returns a Future this should be
// return name.save();
}).then((_) {
/* return */ objectory.close();
}).then((_) {
return display(name);
});
}
I finally solved the riddle using forcemvc and server-side MongoDB:
library serverweb2;
// add code to names() to retrieve all the names
// change names.html accordingly
import "package:forcemvc/force_mvc.dart";
import "package:objectory/objectory_console.dart";
import 'dart:async';
main(List<String> args) {
objectory = new ObjectoryDirectConnectionImpl('mongodb://127.0.0.1/serverweb2',
registerClasses, false);
var app = new WebApplication();
app.start();
// print('Hello world!');
}
#Controller
class Controllers {
#RequestMapping(value: "/", method: RequestMethod.GET)
String index(req, Model model) {
return "index";
}
#RequestMapping(value: "/names", method: RequestMethod.POST)
Future names(ForceRequest req, Model model) {
objectory.initDomainModel().then((_) {
req.getPostParams().then((params) {
var first = params['first'] as String;
var last = params['last'] as String;
var name = new Name()
..firstName = first
..lastName = last;
name.save();
return objectory[Name].find();
}).then((names) {
model.addAttribute('names', names);
return;
}).then((_){
req.async("names");
objectory.close();
});
});
return req.asyncFuture;
}
}
class Name extends PersistentObject {
String get collectionName => "Name";
String get firstName => getProperty("first_name");
set firstName(String value) {
setProperty("first_name", value);
}
String get lastName => getProperty('last_name');
set lastName(String value) {
setProperty('last_name', value);
}
#override
String toString() => "$firstName $lastName";
}
registerClasses() {
objectory.registerClass(Name, () => new Name(), () => new List<Name>());