Exporting VueJS-rendered HTML with checkbox fails to preserve checked state - html

My component template contains the following checkbox code:
<div ref="htmlData">
<input
type="checkbox"
class="mycb"
:id="uniqID"
:disabled="disabled"
v-model="cbvalue"
>
</div>
(parts removed for simplicity).
I need to create a PDF out of this template (on server). This is what i'm doing in the code:
methods : {
save () {
let saveData = {
'html': this.$refs.htmlData.innerHTML
};
this.$http.post('/api/save',saveData);
}
}
However, the saved HTML doesn't contain checkbox state, so it always saves an unchecked checkbox.
Here's a slightly modified jsfiddle.
My question is: how can I capture the checkbox state in the rendered HTML?
I tried adding :checked="cbvalue" prop - no luck

It looks like there's no way to bind the checked attribute of an input; Vue does everything through the property. (For reference, the property is the internal state, the attribute is what shows up in the HTML.)
To get the attribute to reflect the property, you can add a little directive.
var demo = new Vue({
el: '#demo',
data: () => ({
val: false
}),
methods: {
save() {
console.log(this.$refs.main.innerHTML);
}
},
directives: {
explicitChecked: {
update(el) {
if (el.checked) {
el.setAttribute('checked', 'checked');
} else {
el.removeAttribute('checked');
}
}
}
}
})
<script src="//cdnjs.cloudflare.com/ajax/libs/vue/2.4.2/vue.min.js"></script>
<div id="demo">
<button #click="save">save</button>
<div ref="main">
<input type="checkbox" v-model="val" v-explicit-checked>
</div>
</div>

Related

How can I get data from another Vue file?

As the code show below, A.vue file has element data return some number values
<template></template>
<script>
export default {
data(){
return{
element: [
{
number:'11'
}
{
number:'22'
}
]
}
}
}
</script>
Now I want to get element.length from A.vue to B.vue. Is there a way to do that? I saw a solution with button click but i dont want to use button to pass data.
B.vue file
<template>
<div>I want to get element.length here</div>
</template>
You can simply achieve it by passing prop (which contains the length of the element array) from A.vue component to B.vue component. Here is the live demo :
Vue.component('bcomponent', {
// declare the props
props: ['length'],
// just like data, the prop can be used inside templates
// and is also made available in the vm as this.message
template: '<div>Element length: {{ length }}</div>',
});
var app = new Vue({
el: '#app',
data: {
element: [{
number: '11'
}, {
number: '22'
}]
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<!-- Component A -->
<div id="app">
<BComponent :length="element.length">
</BComponent>
</div>
If it's possible, just pass the data as a prop from B to A, this way you can implement any logic on the data.
If it's not, you should use vuex for data storage, so any component can access it.

How to dynamically append HTML element to component in Vue.js

I'm new to vue.js, before this i'm using jquery or js for my project, i'm working on a project that require me to append HTML element dynamically on button click, and at the same time bind the input value to model, similar to:
$(".button").click(function() {
$("#target").append("<input type='hidden' name='data' v-model='inputModel' value='1'/>");
});
But i need this in Vue.js ways.
Here is my code:
data() {
return {
programmeBanner: [],
dropzoneOptions: {
...
...
init: function () {
this.on("success", function(file, response) {
file.previewElement.id = response;
// this is the part that i want to append the html input into
// the .dz-preview is the target that i want to append
$(".dz-preview[id='"+response+"']").append("<input type='hidden' name='"+fileInputName+"[]' v-model='programmeBanner' class='"+fileInputName+"' value='"+response+"'/>");
});
},
...
Here is a sample that i want to achieve, this is in Jquery, i need it in Vue.js
https://jsfiddle.net/041xnfzu/
Hmm I think you're mixing all kinds of code here :)
First off, you shouldn't use jquery inside VueJS. I think that your setup is a little off. You shouldn't define a whole object with functions and event listeners in your vue data object.
That's what Vue components are for, define methods in your methods property and data in you data property.
Thanks to your jsfiddle example, I have this pure vuejs example for you on codepen: https://codepen.io/bergur/pen/vwRJVx
VueJS code:
new Vue({
el: '#demo',
name: 'Adding html',
data() {
return {
inputs: []
}
},
methods: {
addInput() {
this.inputs.push(this.inputs.length+1)
}
},
computed: {
buttonText() {
return this.showInput ? 'Hide input' : 'Show input'
}
}
})
HTML template
<div id="demo">
<button #click="addInput">Add input</button>
<div v-for="(input, index) in inputs">
<input name="data" v-model="inputs[index]" />
</div>
<p>
First value: {{ inputs[0] }}<br />
Second value: {{ inputs[1] }}
</p>
</div>
Here's a walkthrough of the code.
We create a data property called inputs, that is an array.
We create a method called addInput and all that does is to push a new item into the inputs array
In the template we loop with v-for through our inputs array and render a input for each item in our inputs data property.
We then use v-model to bind each input to its corresponding place in the inputs array.
You can try to change the input value and see that the template updates the value.
So input[0] holds the value for the first input, input[1] holds the value for the second input and so on.
If you want only one element to be appended to component, then you should use v-if
https://v2.vuejs.org/v2/guide/conditional.html#v-if
If you want to append multiple elements, like todo list, you should use v-for
https://v2.vuejs.org/v2/guide/#Conditionals-and-Loops

Issue dynamically creating inputs with angular attributes in an ng-repeat loop?

I'm trying to create a directive which takes some JSON data, and creates a form. Each of the inputs in the form are contained in a <div> wrapper. The wrapper also contains a <div> that appears when ng-show is used. The <div> using ng-show is for dynamically displaying errors.
HTML for the Directive:
<div class="input-wrapper" ng-repeat="inputData in contactCtrl.formInputData">
<input type="text" class="text-input" placeholder="{{ inputData.placeholder }}" ng-model="inputData.model" />
<div class="error-bubble" ng-show="inputData.showFunction">
</div>
</div>
HTML on the Page:
<div id="column-right">
<h2>
Send me an Email
</h2>
<contact-form></contact-form>
</div>
Directive Creation:
spaModule.directive("contactForm", function() {
return {
restrict: "E",
templateUrl: "partials/directives/contact-form.html"
}
});
JSON Data and a Validation Function:
this.formInputData = [
{
placeholder: "Name",
model: "contactCtrl.clientName",
showFunction: "!contactCtrl.validateName()"
},
{
placeholder: "Email",
model: "contactCtrl.email",
showFunction: "!contactCtrl.validateEmail()"
},
{
placeholder: "Subject",
model: "contactCtrl.subject",
showFunction: "!contactCtrl.validateSubject()"
}
];
this.validateName = function() {
if (this.clientName !== "") {
this.nameError = "Name is required!";
return true;
} else {
return false;
}
};
Not a single piece of this is working. The placeholder is not being rendered correctly, ng-show is not doing anything, and the ng-model is not working. I've tried reformatting this as normal HTML in my page and everything works flawlessly.
The issue appears to be declaring ng attributes with ng-repeat. What am I doing wrong?

How to create a separate scope isolated from ng-repeat in Angular?

I am new to AngularJS and have some trouble understanding the concept of scope in Angular. I have read some posts on stackoverflow as well as online articles, which advise me to create a custom directive to create an isolate scope, but I am getting nowhere...
As for the project I'm working on, I am trying to make a button that when clicked, will trigger a textarea. However, because of ng-repeat, the textarea is triggered for all buttons while I click only one.
My .js file:
angular.module('myApp')
.controller('myCtrl', function ($scope, Question) {
scope.visible = false;
scope.toggle = function() {
scope.visible = !scope.visible;
};
.directive("myDirective", function () {
return {
scope: {
ngClick: '&',
ngShow: '&'
}
}
});
Here is my HTML file:
<ul>
<li ng-repeat="object in objectList">
<button type="text" myDirective ng-click="toggle()">Click</button>
<textarea myDirective ng-show="visible"></textarea>
</li>
</ul>
Angular is creating child (NOT isolated) scope when ng-repeating, try this out, when you ng-init a variable, it is only visible within that repeat div.
<div ng-repeat="i in [0,1,2,3,4,5,6,7,8,9]" ng-init="visible=false">
<button ng-click="visible=!visible">Toggle</button>
<h1 ng-show="visible">look at me!</h1>
</div>
Plunker
There is no need to use a directive. You need to use object in the foreach to refer each item in the loop.
Add visible to each object in objectList:
$scope.objectList = [
{ visible: false },
{ visible: false },
{ visible: false }
];
Then the toggle button will need to pass the object to toggle:
$scope.toggle = function (object) {
object.visible = !object.visible;
};
The ng-show will need to check object.visible and ng-click will need to pass the object:
<button type="text" ng-click="toggle(object)">Click</button>
<textarea ng-show="object.visible"></textarea>
Plunkr

How to control ng-repeat divs from ng-repeat inputs

So, just getting started in Angular and it's pretty tricky, coming from a pretty simple JS and jQuery background. Here's what I'm trying to do. I have a "tag template" that has a couple categories and then some sub-tags contained within. I have defined these as an object, with the idea that the object/file can be called via file request and manipulated, etc.
I have loaded labels and tag category inputs dynamically by using a factory service and a controller with ng-repeat. Likewise, I have deposited the subtags into another div on page2 (using jQuery mobile page swiping). I'd like to use the checkbox state of the category tags to show/hide the sub-tags on page2.
I have tried dozens of things and searched all over stackexchange, the net, etc, but is simple and straightforward and similar enough for me to get it working. If someone can point me in the right direction, that would be great. Keep in mind that my next step is to add a button on page 1 to add a new category, and buttons on page 2 to add sub-tags to the sub-tag categories.
Finally, I have one more weird thing to report. If I only have two pages in my DOM, I have some weird behavior when loading the page. If I load from page 1, the tag checkboxes do not function, and I see a slight fattening of the border of the labels. If I swipe left to page 2 and reload from this page, the borders of the labels are thin and the checkboxes function. Cannot track down why this would be happening. My hacky workaround is to add an empty page zero and then changepage immediately to page one, but this is far from ideal. Any thoughts on that would be appreciated as well.
Here it is:
HTML
<!-- Angular version -->
<button class="ui-btn" onclick="selectTemplate();">My Template</button>
<form>
<div data-role="controlgroup">
<fieldset data-role="controlgroup">
<div ng-controller="templateCtrl">
<label
class="ui-checkbox"
ng-style="{backgroundColor: '{{tagCat.color | bgColor}}'}"
ng-repeat="tagCat in template"><input type="checkbox"
class="ui-checkbox"
id="{{tagCat.name}}"
ng-model="clicked"
ng-click="click();"
/>{{tagCat.name}}</label>
<div ng-repeat="tagCat in template">{{cb}} {{tagCat.name}} hallo</div>
</div>
</fieldset>
</div>
<div style="display:none" class="flashNotification"></div>
</form>
</div>
<div data-role="page" id="two">
<button class="ui-btn" onclick="selectTemplate();">My Template</button>
<form>
<div data-role="controlgroup">
<div ng-controller="templateCtrl">
<div ng-repeat="tagCat in template" ng-show="clicked" class="{{tagCat.name}}">{{tagCat.name}}
<fieldset data-role="controlgroup">
<label class="ui-checkbox"
ng-repeat="item in tagCat.items"
ng-style="{backgroundColor: '{{tagCat.color | bgColor}}'}"
for="item.name">{{tagCat.color | bgColor}}
<input class="ui-checkbox"
name="{{item.name}}"
id='{{item.name}}'
type="checkbox" />{{item.name}}</label>
</fieldset>
</div>
</div>
</div>
<div style="display:none" class="flashNotification"></div>
</form>
</div>
</div>
JS for jQuery Mobile
$(document).ready(function() {
// addTemplateItems(tagTemplate); // not necessary with Angular
// $.mobile.changePage('#two', { transition: 'none' }); // required or checkboxes don't work on load
$.mobile.changePage('#one', { transition: 'none' });
// // $("[data-role=controlgroup]").controlgroup("refresh");
// set up page nav
$(document).delegate('.ui-page', "swipeleft", function(){
var $nextPage = $(this).next('[data-role="page"]');
var $prevPage = $(this).prev('[data-role="page"]');
console.log("binding to swipe-left on "+$(this).attr('id') );
// swipe using id of next page if exists
if ($nextPage.length > 0) {
$.mobile.changePage($nextPage, { transition: 'slide' });
} else {
var message = 'tagged!';
// save tags here
flashNotify(message);
console.log('fire event!');
$('#flashNotification').promise().done(function () {
$('#group1').hide();
$('#group2').hide();
$('.ui-btn').hide();
// addTemplateItems(tagTemplate);
$.mobile.changePage($prevPage, { transition: 'none' });
captureImage();
});
}
}).delegate('.ui-page', "swiperight", function(){
var $prevPage = $(this).prev('[data-role="page"]');
console.log("binding to swipe-right on "+$(this).attr('id') );
// swipe using id of next page if exists
if ($prevPage .length > 0) {
$.mobile.changePage($prevPage, { transition: 'slide', reverse : true });
} else {
alert('no backy backy!');
}
});
// $("input[type='checkbox']").checkboxradio().checkboxradio("refresh");
});
JS for Angular App
var app = angular.module('STL', []);
app.factory('TagTemplate', [function () {
var TagTemplate = {};
var tagTemplate = {
family: {
name: "family",
description: "These are your family members.",
color: "red",
items: [
{
name: "Joe"
},
{
name: "Mary"
},
{
name: "Jim"
}
]
},
design: {
name: "design",
description: "Different types of design notes.",
color: "blue",
items: [
{
name: "inspiring"
},
{
name: "fail"
},
{
name: "wayfinding"
},
{
name: "graphics"
}
]
},
work: {
name: "work",
description: "Stuff for work.",
color: "green",
items: [
{
name: "whiteboard"
},
{
name: "meeting"
},
{
name: "event"
}
]
}
};
TagTemplate = tagTemplate;
return TagTemplate;
}])
// Controller that passes the app factory
function templateCtrl($scope, TagTemplate) {
$scope.template = TagTemplate;
$scope.click = function(model) {
console.log(this.checked, this.tagCat.name);
}
}
app.filter('bgColor', function () {
return function (color) {
// console.log(color, $.Color(color).lightness(.05).toHexString(.05));
// var rgba = $.Color(color).alpha(.05);
return $.Color(color).lightness(.97).toHexString();
}
})
For the main part, success!
I found a jsfiddle that gave me a good base for experimenting. After some playing, I realized that I just have to create a show property within each of the categories in my data service model, and then assign the ng-model to that property to control it.
I had to do it slightly differently in my own code, but the understanding gained from the following jsfiddle led me to the answer:
http://jsfiddle.net/Y43yP/
HTML
<div ng-app ng-controller="Ctrl">
<div class="control-group" ng-repeat="field in customFields">
<label class="control-label">{{field}}</label>
<div class="controls">
<input type="text" ng-model="person.customfields[field]" />
<label><input type="checkbox" ng-model="person.show[field]" /></label>
</div>
</div>
<button ng-click="collectData()">Collect</button><button ng-click="addField()">Add Field</button><br/><br/>
<em>Booleans</em>
<div ng-repeat="field in customFields">
<p>{{field}}: {{person.show[field]}}</p>
</div>
<em>Show/Hide</em>
<div ng-repeat="field in customFields">
<p ng-show="person.show[field]">{{field}}: {{person.customfields[field]}}</p>
</div>
</div>
JS
function Ctrl($scope) {
$scope.customFields = ["Age", "Weight", "Height"];
$scope.person = {
customfields: {
"Age": 0,
"Weight": 0,
"Height": 0
},
show: {
"Age": false,
"Weight": false,
"Height": false
}
};
$scope.collectData = function () {
console.log($scope.person.customfields, $scope.person.show);
}
$scope.addField = function () {
var newField = prompt('Name your field');
$scope.customFields.push(newField);
}
}
Still having the checkbox issue but I'll open a separate issue for that if I can't figure it out.
Thanks.