dynamic html generation in polymer - polymer

I am trying to generate a div dynamically based on the properties in a fabric.canvas.object.
The following is excerpts from my code.
class Polymer2FabricEdit extends Polymer.Element {
static get is() { return 'polymer-2-fabric-edit' }
static get config() {
return {
properties: {
canvas: {
type: Object,
value: null
},
ctx: {
type: Object,
value: null
},
canvasWidth: {
type: Number,
value: 300
},
canvasHeight: {
type: Number,
value: 300
},
topProperty: {
type: Number,
value: 0,
notify: true
},
observers: [
]
}
}
constructor() {
super();
console.log('created');
}
connectedCallback() {
super.connectedCallback();
console.log('attached');
}
ready() {
super.ready();
this.canvas = new fabric.Canvas(this.$.c, {isDrawingMode: false});
console.log(this.canvas);
this.ctx = this.canvas.getContext('2d');
this.addEventListener('click', (e)=>this.handleClick(e));
this.addEventListener('change', (e)=>this.handleChange(e));
this.$.dropTargetDiv.addEventListener("dragover", (e)=>this.preventEventDefault(e));
this.$.dropTargetDiv.addEventListener("drop", (e)=>this.loadObject(e));
this.$.imageLoader.addEventListener("change", (e)=>this.readURL(e));
this.$.backgroundImageLoader.addEventListener("change", (e)=>this.readBackgroundURL(e));
this.$.textureImageLoader.addEventListener("change", (e)=>this.readTextureURL(e));
console.log('ready');
}
generateEditView(obj) {
console.log("generateEditView");
console.log(obj);
var html = "<br/><br/><b>Edit Properties</b><br/><br/>";
for(var key in obj) {
if(key === "stateProperties") {
console.log(key, obj[key]);
var o = obj[key];
for(var k in o) {
console.log(o[k], obj[o[k]]);
switch(o[k]) {
case 'top':
console.log("setting top from:", this.topProperty);
this.topProperty = obj[o[k]];
html += '<input type="number" value="{{topProperty}}" min="0" max="150" id="topProperty">';
html += '<input type="range" value="{{topProperty}}" min="0" max="150" id="topPropertyRange">';
html += '<br/>';
console.log("set to: " + this.topProperty);
break;
case ‘…’:
break;
default;
}
console.log("adding: " + html);
this.$.editView.innerHTML = html;
}
handleClick(e) {
console.log("hamdleClick: " + e.type);
console.log("click: " + e.currentTarget.tagName);
console.log(e);
var id = e.path[0].id;
console.log("id: " + id);
switch(id) {
case '':
console.log("No ID - This is mostlike a selet of an object");
var target = e.target;
var obj = this.canvas.getActiveObject();
console.log("target:" + target);
console.log("selected Object:" + obj);
console.log("selected Menu: " + this.selectedMenu);
if(this.selectedMenu === "control") {
this.generateEditView(this.canvas.getActiveObject());
}
break;
break;
case 'topProperty':
console.log("Change TopProperty", this.$.topProperty);
this.topProperty = this.$.topProperty;
break;
default:
}
}
This does not work I get the following runtime error
polymer-2-edit.html:1979 The specified value "{{topProperty}}" is not a valid number. The value must match to the following regular expression: -?(\d+|\d+.\d+|.\d+)([eE][-+]?\d+)?
It appears the generated code does not pick up the binding.
If I change the code to the following the div is displayed properly.
html += '';
html += ''
But then I get undefined when reading this,$.topProperty or this.$.topPropertyRange
I have also tried adding onchange=changeProperty(this) on the latter two and I get changeProperty is undefined message in the debugger.
I suspect its expecting the function to be outside of polymer but I need it inside polymer to set the objects properties.
Is there an example of adding dynamic input fields and processing changes that are made?

I still have not been successful in getting dynamic html generated code to work.
I tried rewriting the code using dom:if and it did not work the way I wanted it to.
That said I have accomplished my goal by writing static code and using css to hide unneeded selections and show the sections I need.
I added this css class
.hide {
display: none;
visibility: hidden;
}
Then I created a div for each element and appended “Edit” to the property name for the div id
<div id="topEdit">
<label for="top">Top:</label>
<input type="number" value="{{top::input}}" min="0" max="150" id="top">
<input type="range" value="{{top::input}}" min="0" max="150" id="topRange">
<br/>
</div>
<div id="leftEdit">
<label for="left">Left:</label>
<input type="number" value="{{left::input}}" min="0" max="150" id="left" onchange="{{Change}}">
<input type="range" value="{{left::input}}" min="0" max="150" id="leftRange">
<br/>
</div>
<div id="widthEdit">
<label for="width">Width:</label>
<input type="number" value="{{width::input}}" min="0" max="150" id="width">
<input type="range" value="{{width::input}}" min="0" max="150" id="widtnRange">
<br/>
</div>
I use this function to hide all the elements with an id “Edit”
clearView() {
// hide the Edit Elements
var elements = this.$;
for(var key in elements) {
var element = elements[key];
var elementId = element.id;
if(!!elementId.match(/.*Edit$/)) {
element.classList.add('hide');
}
}
}
The original code then looped thorough the active object and removed the css hide class from the appreciate elements. This approach was modified since some of the html elements chosen did not directly correspond to the objects element, and I decided I did not want to provide fields for every property.
The modified code is based on the selected object type I utilize an array of fields
When the user clicks on an object I execute code similar to the following example.
This example is for text or iText objects.
var fields = [
"fontWeight", // normal, bold
"fontStyle", // normal, italic, oblique, ''
"textDecoration", // underline, linethrough, overline
"fontFamily",
"textAlign",
"stroke",
"backgroundColor",
"textBackgroundColor",
"strokeWidth",
"fontSize",
"lineHeight",
"charSpacing",
"shadow", // shadowColor, shadowBlur, shadowOffsetX, shadowOffsetY
"shadowColor",
"shadowBlur",
"shadowOffsetX",
"shadowOffsetY",
"text",
"addText"
];
The following code removes the css hide class and displays the appropriate elements.
for(var key in fields) {
var id = fields[key] + "Edit";
var element = this.$[id];
if(element) {
element.classList.remove('hide');
}
}
This approach works well for me and provides full two way binding for the selected elements.

Related

.val() is not working and is returning as "on"

Why does this happen ? I am not using correct syntax here?
HTML is as below
<div id="tasks" value="1" class="1">
<input type="checkbox">
<label>Task has been added</label>
</div>
jQuery is as below
$("#cButton").click(function () {
var arr_id = [];
$(":checkbox:checked").each(function (i) {
arr_id[i] = $(this).val();
console.log("$(this).val() : " + $(this).val());
})
if (arr_id.length == 0) {
alert("atleast check one");
} else {
for (var i = 0; i < arr_id.length; i++) {
$("." + arr_id[i]).remove();
console.log("Hello");
}
}
});
Console O/P is as below
$(this).val() : on
The best way to get a boolean value from a checkbox input with jQuery is using prop: $(this).prop("checked").
If the value attribute was omitted, the default value for the checkbox is on
MDN input type="checkbox"
If you need to use the value of the input you should be placing it inside the input tag. Because you didn't set a value it's taking the default "on" when calling val() on it.
$("#cButton").click(function () {
var arr_id = [];
$(":checkbox:checked").each(function (i) {
arr_id[i] = $(this).prop("checked");
console.log($(this).prop("checked"));
})
if (arr_id.length == 0) {
alert("atleast check one");
} else {
for (var i = 0; i < arr_id.length; i++) {
$("." + arr_id[i]).remove();
console.log("Hello");
}
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div id="tasks" value="1" class="1">
<input type="checkbox">
<label>Task has been added</label>
</div>
<button id="cButton">Button</button>
You placed the value attribute on the parent <div>... A <div> does not have a value. It has to be on the input.
The elements which can have a value are ref:
<button>
<data>
<input>
<li>
<meter>
<option>
<progress>
<param>
$("#cButton").click(function() {
var arr_id = [];
$(":checkbox:checked").each(function(i) {
arr_id[i] = $(this).val();
console.log("$(this).val() : " + $(this).val());
})
if (arr_id.length == 0) {
alert("atleast check one");
} else {
for (var i = 0; i < arr_id.length; i++) {
$("." + arr_id[i]).remove();
console.log("Hello");
}
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div id="tasks" class="1">
<input type="checkbox" value="1">
<label>Task has been added</label>
</div>
<button id="cButton">Button</button>

Filter json file with tickboxes in Angular

I am trying to filter results from my JSON file so the user can click on ladies or mens 'styles', but some products have both a ladies and mens styles, so how would I be able to show the results for 'both' without having three tick boxes? I know I cannot have duplicate names in my JSON, but have done this just as an example for now. Any help would be appreciated. I have put my code here:
http://plnkr.co/edit/fXOZHqo48ntvsdJA875y?p=preview
<div ng-controller="myCtrl">
<div ng-repeat="(prop, ignoredValue) in { 'category': true, 'cut': true }" ng-init="filter[prop]={}">
<b>{{prop | capitalizeFirst}}:</b><br />
<span class="quarter" ng-repeat="opt in getOptionsFor(prop)">
<b><input type="checkbox" ng-model="filter[prop][opt]" /> {{opt}}</b>
</span>
<hr />
</div>
<div ng-repeat="w in filtered=(products | filter:filterByProperties)">
{{w.name}} ({{w.category}})
</div>
<hr />
Number of results: {{filtered.length}}
</div>
You need to make couple of changes in your json.
Whenever there is cut has one value then it would be string & whenever there is more than one value then you could maintain an array
like "cut": "ladies", "cut": "mens", & "cut": ["ladies","mens"]
You need to handle string and filter thing in your filterByProperties function
Markup
<span ng-show="!isArray(opt)" class="quarter" ng-repeat="opt in getOptionsFor(prop)">
<b><input type="checkbox" ng-model="filter[prop][opt]" /> {{opt}}</b>
</span>
Code
$scope.isArray = function(val) {
return angular.isArray(val)
}
$scope.filterByProperties = function(product) {
// Use this snippet for matching with AND
var matchesAND = true;
for (var prop in $scope.filter) {
if (noSubFilter($scope.filter[prop])) continue;
if (!$scope.isArray(product[prop])) { //if its not an array
if (!$scope.filter[prop][product[prop]]) {
matchesAND = false;
break;
}
} else {
//if its an array
for (var i = 0; i < product[prop].length; i++) {
var anyPropMatch = false;
if ($scope.filter[prop][product[prop][i]]) {
anyPropMatch = true;
break;
}
}
if (!anyPropMatch) {
matchesAND = false;
break;
}
}
}
return matchesAND;
};
Working Plunkr

Use HTML5 (datalist) autocomplete with 'contains' approach, not just 'starts with'

(I can't find it, but then again I don't really know how to search for it.)
I want to use <input list=xxx> and <datalist id=xxx> to get autocompletion, BUT I want the browser to match all options by 'contains' approach, instead of 'starts with', which seems to be standard. Is there a way?
If not simply, is there a way to force-show suggestions that I want to show, not those that the browser matched? Let's say I'm typing "foo" and I want to show options "bar" and "baz". Can I force those upon the user? If I just fill the datalist with those (with JS), the browser will still do its 'starts with' check, and filter them out.
I want ultimate control over HOW the datalist options show. NOT over its UI, flexibility, accessibility etc, so I don't want to completely remake it. Don't even suggest a jQuery plugin.
If I can ultimate-control form element validation, why not autocompletion, right?
edit: I see now that Firefox does use the 'contains' approach... That's not even a standard?? Any way to force this? Could I change Firefox's way?
edit: I made this to illustrate what I'd like: http://jsfiddle.net/rudiedirkx/r3jbfpxw/
HTMLWG's specs on [list]
W3's specs on datalist
DavidWalsh example
HONGKIAT's summary on behaviors..?
'contains' approach
Maybe this is what you are looking for (part 1 of your question).
It goes with the limitation of "starts with" and changes when a selection is made.
'use strict';
function updateList(that) {
if (!that) {
return;
}
var lastValue = that.lastValue,
value = that.value,
array = [],
pos = value.indexOf('|'),
start = that.selectionStart,
end = that.selectionEnd,
options;
if (that.options) {
options = that.options;
} else {
options = Object.keys(that.list.options).map(function (option) {
return that.list.options[option].value;
});
that.options = options;
}
if (lastValue !== value) {
that.list.innerHTML = options.filter(function (a) {
return ~a.toLowerCase().indexOf(value.toLowerCase());
}).map(function (a) {
return '<option value="' + value + '|' + a + '">' + a + '</option>';
}).join();
updateInput(that);
that.lastValue = value;
}
}
function updateInput(that) {
if (!that) {
return;
}
var value = that.value,
pos = value.indexOf('|'),
start = that.selectionStart,
end = that.selectionEnd;
if (~pos) {
value = value.slice(pos + 1);
}
that.value = value;
that.setSelectionRange(start, end);
}
document.getElementsByTagName('input').browser.addEventListener('keyup', function (e) {
updateList(this);
});
document.getElementsByTagName('input').browser.addEventListener('input', function (e) {
updateInput(this);
});
<input list="browsers" name="browser" id="browser" onkeyup="updateList();" oninput="updateInput();">
<datalist id="browsers">
<option value="Internet Explorer">
<option value="Firefox">
<option value="Chrome">
<option value="Opera">
<option value="Safari">
</datalist>
Edit
A different approach of displaying the search content, to make clear, what happens. This works in Chrome as well. Inspired by Show datalist labels but submit the actual value
'use strict';
var datalist = {
r: ['ralph', 'ronny', 'rudie'],
ru: ['rudie', 'rutte', 'rudiedirkx'],
rud: ['rudie', 'rudiedirkx'],
rudi: ['rudie'],
rudo: ['rudolf'],
foo: [
{ value: 42, text: 'The answer' },
{ value: 1337, text: 'Elite' },
{ value: 69, text: 'Dirty' },
{ value: 3.14, text: 'Pi' }
]
},
SEPARATOR = ' > ';
function updateList(that) {
var lastValue = that.lastValue,
value = that.value,
array,
key,
pos = value.indexOf('|'),
start = that.selectionStart,
end = that.selectionEnd;
if (lastValue !== value) {
if (value !== '') {
if (value in datalist) {
key = value;
} else {
Object.keys(datalist).some(function (a) {
return ~a.toLowerCase().indexOf(value.toLowerCase()) && (key = a);
});
}
}
that.list.innerHTML = key ? datalist[key].map(function (a) {
return '<option data-value="' + (a.value || a) + '">' + value + (value === key ? '' : SEPARATOR + key) + SEPARATOR + (a.text || a) + '</option>';
}).join() : '';
updateInput(that);
that.lastValue = value;
}
}
function updateInput(that) {
var value = that.value,
pos = value.lastIndexOf(SEPARATOR),
start = that.selectionStart,
end = that.selectionEnd;
if (~pos) {
value = value.slice(pos + SEPARATOR.length);
}
Object.keys(that.list.options).some(function (option) {
var o = that.list.options[option],
p = o.text.lastIndexOf(SEPARATOR);
if (o.text.slice(p + SEPARATOR.length) === value) {
value = o.getAttribute('data-value');
return true;
}
});
that.value = value;
that.setSelectionRange(start, end);
}
document.getElementsByTagName('input').xx.addEventListener('keyup', function (e) {
updateList(this);
});
document.getElementsByTagName('input').xx.addEventListener('input', function (e) {
updateInput(this);
});
<input list="xxx" name="xx" id="xx">
<datalist id="xxx" type="text"></datalist>
yet this thread is posted about 2 years ago. but if you are reading this thread, you maybe need to check a newer version of your browser:
Current specification: https://html.spec.whatwg.org/multipage/forms.html#the-list-attribute
User agents are encouraged to filter the suggestions represented by
the suggestions source element when the number of suggestions is
large, including only the most relevant ones (e.g. based on the user's
input so far). No precise threshold is defined, but capping the list
at four to seven values is reasonable. If filtering based on the
user's input, user agents should use substring matching against both
the suggestions' label and value.
And when this post written, behavior of Firefox (51) and Chrome (56) had already been changed to match the specification.
which means what op want should just work now.
this fiddle here has cracked what you are asking for
But I am not sure how to make it work without this dependency as the UI looks bit odd and out of place when used along with Bootstrap.
elem.autocomplete({
source: list.children().map(function() {
return $(this).text();
}).get()
I found this question because I wanted "starts with" behavior, and now all the browsers seem to implement "contains". So I implemented this function, which on Firefox (and probably others), if called from input event handler (and optionally, from focusin event handler) provides "starts with" behavior.
let wrdlimit = prefix =>
{ let elm = mydatalist.firstElementChild;
while( elm )
{ if( elm.value.startsWith( prefix ))
{ elm.removeAttribute('disabled');
} else
{ elm.setAttribute('disabled', true );
}
elm = elm.nextElementSibling;
}
}

watch changes on JSON object properties

I'm trying to implement a directive for typing money values.
var myApp = angular.module('myApp', []);
var ctrl = function($scope) {
$scope.amount = '0.00';
$scope.values = {
amount: 0.00
};
};
myApp.directive('currency', function($filter) {
return {
restrict: "A",
require: "ngModel",
scope: {
separator: "=",
fractionSize: "=",
ngModel: "="
},
link: function(scope, element, attrs) {
if (typeof attrs.separator === 'undefined' ||
attrs.separator === 'point') {
scope.separator = ".";
} else {
scope.separator = ",";
};
if (typeof attrs.fractionSize === 'undefined') {
scope.fractionSize = "2";
};
scope[attrs.ngModel] = "0" + scope.separator;
for(var i = 0; i < scope.fractionSize; i++) {
scope[attrs.ngModel] += "0";
};
scope.$watch(attrs.ngModel, function(newValue, oldValue) {
if (newValue === oldValue) {
return;
};
var pattern = /^\s*(\-|\+)?(\d*[\.,])$/;
if (pattern.test(newValue)) {
scope[attrs.ngModel] += "00";
return;
};
}, true);
}
};
});
HTML template:
<div ng-app="myApp">
<div ng-controller="ctrl">
{{amount}}<br>
<input type="text" style="text-align: right;" ng-model="amount" currency separator="point" fraction-size="2"></input>
</div>
</div>
I want to bind the value in my input element to values.amount item in controller but the watch instruction of my directive doesn't work.
How do I leverage two-way-data-binding to watch JSON objects?
To understand problem more precise I've created a jsfiddle.
The task is the following: Add extra zeros to the input element if user put a point. I mean if the value in input element say "42" and user put there a point, so the value now is "42." two extra zeros have to be aded like this "42.00".
My problems:
If I use ng-model="amount" the logic in input element works, but amount value of outer controller doesn't update.
If I use ng-model="values.amount" for binding, neither amount of outer controller nor input element logic works.
I really have to use ng-model="values.amount" instruction, but it doesn't work and I don't know why.
Any ideas?

DropDown Menu Changing OnChange

I have 3 dropdown menu. and all 3 are interlinked. ie, if i select the values of 1st dropdown, depending on that the second dropdown should display values. depending on the selection of 2nd dropdown, 3rd dropdown should populate the values. have done for 1st and 2nd. but depending on the values of 2nd drop down am not able to populate the values of 3rd dropdown. can anyone plaz help me. i know something like this will be in JFIDDLE.com. but not able to fine the exact name to search that!
You have to use AJAX if you want that. it will be easy.
<select name="ID"
id="ID"
onchange="DoYourTaskHere(this);">
<option value="select" selected="selected">Select</option>
<c:forEach items="${A.List}" var="Variable">
<option value="${ID}">
<c:out value="${ID}" />
</option>
</c:forEach>
</select>
And in the script you write the code as follows.
function loadValue(ID) {
if (ID.value != "select") {
if (window.XMLHttpRequest) {// code for IE7+, Firefox, Chrome, Opera,
// Safari
ValueXmlHttpReq = new XMLHttpRequest();
} else {// code for IE6, IE5
ValueXmlHttpReq = new ActiveXObject("Microsoft.XMLHTTP");
}
ValueXmlHttpReq.onreadystatechange = processLoadValues;
ValueXmlHttpReq.open("POST", "getValue.htm?ID="
+ ID.value, true);
ValueXmlHttpReq.send();
} else {
var objSelect = document.getElementById("ValueId");
var currentValueListLength = objSelect.options.length;
while (currentValueListLength > 0) {
objSelect.remove(1);
currentValueListLength--;
}
var objSelect = document.getElementById("2ndDropDownWhereYouWantToPopulate");
var currentSecondValueListLength = objSelect.options.length;
while (currentSecondValueListLength > 0) {
objSelect.remove(1);
currentSecondValueListLength--;
}
}
}
Allright , here something to get you started :
<form name='cars'>
<select name='brand'></select>
<select name='model'></select>
</form>
​
and the javascript (i'm using jQuery ) :
var application_model = [
{
name: "General motors",
models: [
"model1", "model2", "model3"
]},
{
name: "Mercedes",
models: [
"model4", "model5", "model6"
]},
{
name: "Fiat",
models: [
"model7", "model8", "model9"
]}
];
var selectedBrandIndex = 0
var selectedModelIndex = 0
function render() {
// render the first combo
$('select[name=brand]').empty();
$.each(application_model, function(index, object) {
var selected = "";
if (index == selectedBrandIndex) {
selected = "selected";
}
console.log(this);
$('select[name=brand]').append("<option value='" + index + "' " + selected + ">" + object.name + "</option>");
})
// render the second combo
$('select[name=model]').empty();
$.each(application_model[selectedBrandIndex].models, function(index, object) {
var selected = "";
if (index == selectedModelIndex) {
selected = "selected";
}
console.log(this);
$('select[name=model]').append("<option value='" + index + "' " + selected + ">" + object + "</option>");
});
}
function main() {
$("select[name=brand]").bind("change", function(event) {
console.log(event.currentTarget.value);
selectedBrandIndex = event.currentTarget.value;
render();
});
render();
}
main();​
check the fiddle here :
http://jsfiddle.net/camus/MAgza/2/
cheers
you can try related combobox
You can easily setup any number of combobox instances on a single page. They can interact with each other based on certain client or server side events.
This example shows how the comboboxes can interact with each other using client-side methods and requesting the items on demand. To request the items on demand at the client-side, the requestItems() method is used.
The ViewState of the dependent comboboxes is disabled because the data required for their proper operation in this example is maintained in their ClientState.
refer http://demos.telerik.com/aspnet-ajax/combobox/examples/functionality/multiplecomboboxes/defaultcs.aspx