I'm learning vuejs and trying to do all without jquery
I need to get a value of a css style line-height.
In jquery i would do:
let x = $(this).css("line-height");
How can I get this value using vuejs 2.5?
I was exploring this.$el in this structure, but can't find solution to get this value:
data: function () {
return {
lineHeight: null
}
},
mounted(){
this.lineHeight = ?
}
tl;dr
// with jQuery: $(this).css("line-height");
// with Vue:
mounted() {
this.lineHeight = window.getComputedStyle(this.$el).getPropertyValue('line-height');
}
If the component (this.$el) may be inside an iframe or popup, or if you want to be extra careful, read on.
JSFiddle demo here.
new Vue({
el: '#app',
data: {
lineHeightTLDR: '',
lineHeightFull: '',
},
mounted(){
this.lineHeightTLDR = window.getComputedStyle(this.$el).getPropertyValue('line-height');
this.lineHeightFull = this.css('line-height');
},
methods: {
css(propertyName) {
let view = this.$el.ownerDocument.defaultView;
if ( !view || !view.opener ) {
view = window;
}
let computed = view.getComputedStyle(this.$el);
return computed.getPropertyValue(propertyName) || computed[propertyName];
}
}
})
<script src="https://unpkg.com/vue"></script>
<div id="app">
<pre>lineHeight tl;dr..: {{ lineHeightTLDR }}</pre>
<pre>lineHeight full...: {{ lineHeightFull }}</pre>
</div>
Background
Simplest way to mimic jQuery is just to take a look at its source. The returned value from .css() is, roughly:
ret = computed.getPropertyValue( name ) || computed[ name ];
Which uses CSSStyleDeclaration.getPropertyValue on computed. And computed is:
return function( elem ) {
var view = elem.ownerDocument.defaultView;
if ( !view || !view.opener ) {
view = window;
}
return view.getComputedStyle( elem );
}
Which uses Window.getComputedStyle() As you can see, the returned value is something around:
ret = view.getComputedStyle(elem).getPropertyValue( name ) || view.getComputedStyle(elem)[name];
Where view is most probably window but could be something else (elem.ownerDocument.defaultView).
In the end of the day, if you want to be extra certain and do very close to jQuery.css(), use:
// with jQuery: $(this).css("line-height");
// with Vue:
mounted(){
this.lineHeight = this.css('line-height');
},
methods: {
css(propertyName) {
let view = elem.ownerDocument.defaultView;
if ( !view || !view.opener ) {
view = window;
}
let computed = view.getComputedStyle(this.$el);
ret = computed.getPropertyValue(propertyName) || computed[propertyName];
}
}
But if you know your usage does not rely on iframes or popups (as it is very unusual for a Vue instance JavaScript code to run at a window and have the $el it is attached to on another), go with the tl;dr version.
Related
Context: I have a list of posts with tags, categories from wordpress api. I display these posts with Vue and using computed with a search box to filter the result based on titre, description, tags, and categories
Problem: I am trying to update a computed list when user click on a list of tag available. I add the get and set for computed data like this:
var vm = new Vue({
el: '#blogs',
data: {
search: '',
posts: [],
filterPosts: []
},
beforeMount: function() {
// It should call the data and update
callData();
},
computed: {
filterPosts: {
get: function() {
var self = this;
return self.posts.filter(function(post){
var query = self.search.toLowerCase();
var title = post.title.toLowerCase();
var content = post.content.toLowerCase();
var date = post.date.toLowerCase();
var categories = '';
post.categories.forEach(function(category) {
categories += category.name.toLowerCase();
});
var tags = '';
post.tags.forEach(function(tag){
tags += tag.name.toLowerCase();
});
return title.indexOf(query) !== -1 ||content.indexOf(query) !== -1 || date.indexOf(query) !== -1 || categories.indexOf(query) !== -1 || tags.indexOf(query) !== -1;
});
},
set: function (newValue) {
console.log(newValue);
this.filterPosts = Object.assign({}, newValue);
}
}
},
methods: {
filterByTag: function(tag, event) {
event.preventDefault();
var self = this;
self.filterPosts = self.posts.filter(function(post){
var tags = '';
post.tags.forEach(function(tag){
tags += tag.name.toLowerCase();
});
return tags.indexOf(tag.toLowerCase()) !== -1;
});
}
}
}); // Vue instance
The console.log always output new data based on the function I wrote on methods but Vue didn't re-render the view. I think I didn't do the right way or thought like Vue. Could you please give some insight?
Edit 1
Add full code.
I tried to add filterPosts in data but I received this error from Vue: The computed property "filterPosts" is already defined in data.
Your setter is actually not setting anything, it only logs the new value. You need to store it somewhere.
For example you can store it in the component's data:
data: {
value: 'foo',
},
computed: {
otherValue: {
get() { /*...*/ },
set(newVal) { this.value = newVal },
},
},
But this is definitely not the only possibility, if you use Vuex, the setter can dispatch an action that will then make the computed value get updated. The component will eventually catch the update and show the new value.
computed: {
value: {
get() {
return this.$store.getters.externalData;
},
set(newVal) {
return this.$store.dispatch('modifyingAction', newVal);
},
},
},
The bottomline is you have to trigger a data change in the setter, otherwise your component will not be updated nor will it trigger any rerender.
EDIT (The original answer was updated with full code):
The answer is that unless you want to manually change the list filteredPosts without altering posts, you don't need a get and set function for your computed variable. The behaviour you want can be acheived with this:
const vm = new Vue({
data() {
return {
search: '',
posts: [],
// these should probably be props, or you won't be able to edit the list easily. The result is the same anyway.
};
},
computed: {
filteredPosts() {
return this.posts.filter(function(post) {
... // do the filtering
});
},
},
template: "<ul><li v-for='post in filteredPosts'>{{ post.content }}</li></ul>",
});
This way, if you change the posts or the search variable in data, filteredPosts will get recomputed, and a re-render will be triggered.
After going around and around, I found a solution, I think it may be the right way with Vue now: Update the computed data through its dependencies properties or data.
The set method didn't work for this case so I add an activeTag in data, when I click on a tag, it will change the activeTag and notify the computed filterPost recheck and re-render. Please tell me if we have another way to update the computed data.
var vm = new Vue({
el: '#blogs',
data: {
search: '',
posts: [],
tags: [],
activeTag: ''
},
beforeMount: function() {
// It should call the data and update
callData();
},
computed: {
filterPosts: {
get: function() {
var self = this;
return self.posts.filter(function(post){
var query = self.search.toLowerCase();
var title = post.title.toLowerCase();
var content = post.content.toLowerCase();
var date = post.date.toLowerCase();
var categories = '';
post.categories.forEach(function(category) {
categories += category.name.toLowerCase();
});
var tags = '';
post.tags.forEach(function(tag){
tags += tag.name.toLowerCase();
});
var activeTag = self.activeTag;
if (activeTag !== '') {
return tags.indexOf(activeTag.toLowerCase()) !== -1;
}else{
return title.indexOf(query) !== -1 ||content.indexOf(query) !== -1 || date.indexOf(query) !== -1 || categories.indexOf(query) !== -1 || tags.indexOf(query) !== -1;
}
});
},
set: function (newValue) {
console.log(newValue);
}
}
},
methods: {
filterByTag: function(tag, event) {
event.preventDefault();
var self = this;
self.activeTag = tag;
}
}
}); // Vue instance
Try something like:
data: {
myValue: 'OK'
},
computed: {
filterPosts: {
get: function () {
return this.myValue + ' is OK'
}
set: function (newValue) {
this.myValue = newValue
}
}
}
More:
https://v2.vuejs.org/v2/guide/computed.html#Computed-Setter
I've been trying to cycle through colors using a custom component.
<script>
AFRAME.registerComponent('floor-cycle', {
init: function () {
var sceneEl = document.querySelector('a-scene');
var floorEl = sceneEl.querySelector('#floor')
status = 1;
floorEl.addEventListener('click', function () {
if(status==1) {
floorEl.setAttribute('color', 'red'); status = 2
}
else if(status==2) {
floorEl.setAttribute('color', 'blue'); status = 3;
}
else if(status==3) {
floorEl.setAttribute('color', 'green'); status = 1
}
}
);
}
});
</script>
The component uses status to set the color attribute on click event, however this seems inefficient. Can this be implemented using an array rather than status?
demo - https://codepen.io/MannyMeadows/pen/GWzJRB
You can make an array ['red','green','blue'] and Cycle through it:
colors = ['red','green','blue'];
let i = 0;
floorEl.addEventListener('click',function(){
floorEl.setAttribute('material','color', colors[i]);
function add(){(i==colors.length-1) ? i = 0 : i++;}
add();
});
Seems better as the array is now dynamic, not sure how about the performance.
working fiddle here: https://jsfiddle.net/gftruj/g9wfLgab/2/
I've encountered a problem with rendering some elements in React.
(I use ImmutableJS)
renderComponents: function(components) {
if(components.isEmpty()) return [];
var table = [];
components.map(function(component) {
table.push(<ComponentTableElement key={ component.get('id') } data={ component } />);
if(component.has('children')) {
var children = component.get('children');
table.concat(this.renderComponents(children));
}
});
return table;
},
As I looked for error, I found that this.renderComponents(children) doesn't return anything at all and the code somehow stops.
I mean before that line everything works ok, but then after this line, when i try to console.log something, it doesn't show up. And it doesn't even reach return table.
So what is wrong with that code?
In the context of the function you pass to map, this refers to the window object, not to the current component instance, so this.renderComponents is undefined when you try to call it.
components.map(function(component) {
this === window;
});
You can pass a value to use as this in the body of your function as the second parameter of Array::map.
components.map(function(component) {
table.push(<ComponentTableElement key={ component.get('id') } data={ component } />);
if(component.has('children')) {
var children = component.get('children');
// here, `this` refers to the component instance
table.concat(this.renderComponents(children));
}
}, this);
If you're using ES6, you can also use fat-arrow functions, which are automatically bound to this.
components.map((component) => {
table.push(<ComponentTableElement key={ component.get('id') } data={ component } />);
if(component.has('children')) {
var children = component.get('children');
// here, `this` refers to the component instance
table.concat(this.renderComponents(children));
}
});
All, Forgive me I am not familiar with the ASP.NET Ajax. I knew the method Create is attaching an html element to ajax component. But I don't know how to detach it from the current component . and attach another one.
Let's say there is a element ctl00_PlaceHolderMain_UserRegistration_txbPassword1 has been attached to a component type AccelaWebControlExtender.HelperBehavior, and the created component id is ctl00_PlaceHolderMain_UserRegistration_txbPassword1_helper_bhv. The code looks like below. please review it .
Sys.Application.add_init(function() {
$create(AccelaWebControlExtender.HelperBehavior, {"closeTitle":"Close","id":"ctl00_PlaceHolderMain_UserRegistration_txbPassword1_helper_bhv","isRTL":false,"title":"Help"}, null, null, $get("ctl00_PlaceHolderMain_UserRegistration_txbPassword1"));
});
I think firstly I should retrieve the component by id, then do the detach and attach work. Hope someone can give me some help.Thanks.
After doing some research, I found It is called Extend Web server control that encapsulates a client behavior in Asp.net Ajax, And I found the attachment of component is done by Asp.net automatically . We can see the Sys.Application.add_init(function() code is generated in the aspx page by Asp.net automatically. So if we want to customize the original behavior of Web Server Control, I believe it can be made in the Javascript OOP way(old and same).
For example :
If the original behavior code is blow.
// Register the namespace for the control.
Type.registerNamespace('Samples');
//
// Define the behavior properties.
//
Samples.FocusBehavior = function(element) {
Samples.FocusBehavior.initializeBase(this, [element]);
this._highlightCssClass = null;
this._nohighlightCssClass = null;
}
//
// Create the prototype for the behavior.
//
Samples.FocusBehavior.prototype = {
initialize : function() {
Samples.FocusBehavior.callBaseMethod(this, 'initialize');
$addHandlers(this.get_element(),
{ 'focus' : this._onFocus,
'blur' : this._onBlur },
this);
this.get_element().className = this._nohighlightCssClass;
},
dispose : function() {
$clearHandlers(this.get_element());
Samples.FocusBehavior.callBaseMethod(this, 'dispose');
},
//
// Event delegates
//
_onFocus : function(e) {
if (this.get_element() && !this.get_element().disabled) {
this.get_element().className = this._highlightCssClass;
}
},
_onBlur : function(e) {
if (this.get_element() && !this.get_element().disabled) {
this.get_element().className = this._nohighlightCssClass;
}
},
//
// Behavior properties
//
get_highlightCssClass : function() {
return this._highlightCssClass;
},
set_highlightCssClass : function(value) {
if (this._highlightCssClass !== value) {
this._highlightCssClass = value;
this.raisePropertyChanged('highlightCssClass');
}
},
get_nohighlightCssClass : function() {
return this._nohighlightCssClass;
},
set_nohighlightCssClass : function(value) {
if (this._nohighlightCssClass !== value) {
this._nohighlightCssClass = value;
this.raisePropertyChanged('nohighlightCssClass');
}
}
}
// Optional descriptor for JSON serialization.
Samples.FocusBehavior.descriptor = {
properties: [ {name: 'highlightCssClass', type: String},
{name: 'nohighlightCssClass', type: String} ]
}
// Register the class as a type that inherits from Sys.UI.Control.
Samples.FocusBehavior.registerClass('Samples.FocusBehavior', Sys.UI.Behavior);
if (typeof(Sys) !== 'undefined') Sys.Application.notifyScriptLoaded();
I think we can override some of the methods of the Javascript Object Samples.FocusBehavior and it's prototype object to achieve customization.
For example .
I can override Samples.FocusBehavior.prototype._onFocus in the script like this.
Samples.FocusBehavior.prototype._onFocus = function (e) {
alert('test');
if (this.get_element() && !this.get_element().disabled) {
this.get_element().className = this._highlightCssClass;
}
};
Just make sure this code is parsed after original one by Browser.
I am not sure if this is the right way to make it . I hope someone can help to verify it .Thank you very much.
Here is a tutorial of it. please review it .
Cheers.
I have a standard template in an Html file like:
<template name="cards">
{{#each all_cards}}
{{> card_item}}
{{/each}}
</template>
<template name="card_item">
<div class="card" style="left:{{position.x}}px; top:{{position.y}}px">
{{title}}
</div>
</template>
I want to have the cards (css selector .card) become draggable with JQuery.
Now since Meteor automagically updates the DOM using the template, when and how do I know where to call .draggable() on what??
EDIT: This is so far my solution which makes pending movements on other client visible with a wobble animation (using CSS3):
Template.card_item.events = {
'mouseover .card': function (e) {
var $target = $(e.target);
var $cardContainer = $target.hasClass('card') ? $target : $target.parents('.card');
$cardContainer.draggable({containment: "parent", distance: 3});
},
'dragstart .card': function (e) {
Session.set("dragging_id", e.target.id);
$(e.target).addClass("drag");
pos = $(e.target).position();
Events.insert({type: "dragstart", id:e.target.id, left: pos.left, top: pos.top});
},
'dragstop .card': function (e) {
pos = $(e.target).position();
Events.insert({type: "dragstop", id:e.target.id, left: pos.left, top: pos.top});
Cards.update(e.target.id, {$set: {left:pos.left, top:pos.top}});
Session.set("dragging_id", null);
}
}
Events.find().observe({
added: function(event) {
if (event.type == "dragstart" && !Session.equals("dragging_id", event.id)) {
$("#"+event.id).draggable({disabled: true});
$("#"+event.id).addClass("wobble");
}
if (event.type == "dragstop" && !Session.equals("dragging_id", event.id)) {
$("#"+event.id).animate({left: event.left, top: event.top}, 250);
Events.remove({id:this.id});
$("#"+event.id).draggable({disabled: false});
}
}
});
EDIT: This approach doesn't seem to work in the latest versions of Meteor, e.g. v0.5.0. See my comment below.
Looks like we're working on similar things! I've got a working proof of concept for a simple Magic: The Gathering app. Here's how I have dragging implemented at the moment:
In a <head> section in one of your html files, include the jQuery UI script:
<script src="jquery-ui-1.8.20.custom.min.js"></script>
Then, in a js file, make sure elements become draggable on mouseover (note: this is sub-optimal on touchscreens since it requires two touches to drag... I'm looking for a better touchscreen solution):
Template.card_item.events['mouseover .card, touchstart .card'] = function (e) {
var $target = $(e.target);
if (!$target.data('isDraggable')) {
$target.data('isDraggable', true).draggable();
}
};
And finally, handle the drag and dragstop events:
var prevDraggedTime = 0
Template.card_item.events['drag .card'] = function (e) {
// get the cardId from e
var now = new Date().getTime();
var position;
if (now - prevDraggedTime > 250) {
position = $(e.target).position();
Cards.update(cardId, {$set: {x: position.top, y: position.left}});
prevDraggedTime = now;
}
}
Template.card_item.events['dragstop .card'] = function (e) {
// get the cardId from e
var position = $(e.target).position();
Cards.update(cardId, {$set: {x: position.top, y: position.left}});
}