I am creating undo/redo functionality in VueJS. I watch the settings and add a new element to an array of changes when the settings change. I also have a method for undo when the undo button is clicked.
However, when the button is clicked and the last setting is reverted, the settings are changed and the watch is fired again.
How can I prevent a new element being added to the array of changes if the settings changed but it was because the Undo button was clicked?
(function () {
var Admin = {};
Admin.init = function () {
};
var appData = {
settings: {
has_border: true,
leave_reviews: true,
has_questions: true
},
mutations: [],
mutationIndex: null,
undoDisabled: true,
redoDisabled: true
};
var app = new Vue({
el: '#app',
data: appData,
methods: {
undo: function() {
if (this.mutations[this.mutationIndex - 1]) {
let settings = JSON.parse(this.mutations[this.mutationIndex - 1]);
this.settings = settings;
this.mutationIndex = this.mutations.length - 1;
console.log (settings);
}
},
redo: function() {
}
},
computed: {
border_class: {
get: function () {
return this.settings.has_border ? ' rp-pwb' : ''
}
},
undo_class: {
get: function () {
return this.undoDisabled ? ' disabled' : ''
}
},
redo_class: {
get: function () {
return this.redoDisabled ? ' disabled' : ''
}
}
},
watch: {
undoDisabled: function () {
return this.mutations.length;
},
redoDisabled: function () {
return this.mutations.length;
},
settings: {
handler: function () {
let mutation = JSON.stringify(this.settings),
prevMutation = JSON.stringify(this.mutations[this.mutations.length-1]);
if (mutation !== prevMutation) {
this.mutations.push(mutation);
this.mutationIndex = this.mutations.length - 1;
this.undoDisabled = false;
}
},
deep: true
}
}
});
Admin.init();
})();
Since you make the changes with a button click, you can create a method to achieve your goal instead of using watchers.
methods: {
settings() {
// call this method from undo and redo methods if the conditions are met.
// move the watcher code here.
}
}
BTW,
If you don't use setter in computed properties, you don't need getters, so that is enough:
border_class() {
return this.settings.has_border ? ' rp-pwb' : ''
},
These watchers codes look belong to computed:
undoDisabled() {
return this.mutations.length;
},
redoDisabled() {
return this.mutations.length;
},
I am working on a small scale app that displays videos in multiple ways using a video-player component.
Currently I am implementing a stack-list, which is a container that holds video-stack components, and each stack contains one or more video-player components.
While the correct videos are loaded from the DOM, there is a noticeable multi-second lag (in terms of keyboard response) which seems to be related to the ending of the currently played video and the fetching of the next video in the stack.
How can I get rid of this lag? Videos are able to be toggled/selected via mouse hovers or WASD keyboard commands (A: previous, D: next), and the lag can cause a delay in keyboard inputs being registered.
video-stack.hbs
{{video-player highlightedStyle=(string-append stackStyle borderStyle)
looping=(is-single-video videos) videoPos=selectedVidPos
isMuted=(if (video-selected key selectedStackIndex) isMuted true)
url=(if curVideo.teaser.isUrl curVideo.teaser.fileIdentifier
(make-local-url modelIdentifier curVideo.teaser.fileIdentifier))
onClickCallback=(action 'stackClicked')
onHoverCallback=(action 'stackHovered')
onEndedCallback=(action 'getNextVid')}}
video-stack.js
import Ember from 'ember';
export default Ember.Component.extend({
selectedVidPos: 0,
selectedStackIndex: 0,
stackStyle: '',
playerSize: '',
isMuted: true,
init() {
this._super(...arguments);
switch(this.get('videos').length){
case 1:
break;
case 2:
this.set('stackStyle', 'vid-shadows--2');
break;
case 3:
this.set('stackStyle', 'vid-shadows--3');
break;
case 4:
this.set('stackStyle', 'vid-shadows--4');
break;
default:
this.set('stackStyle', 'vid-shadows--4');
break;
}
},
curVideo: Ember.computed('videos', 'selectedVidPos', function () {
return this.get('videos')[this.get('selectedVidPos')];
}),
actions: {
stackClicked() {
this.get('onClickCallback') (this.get('videos'), this.get('selectedVidPos'));
},
getNextVid() {
let arrayLength = this.get('videos').length;
//check if there is only 1 video in the stack
if (arrayLength === 1) {
return;
}
let curArrayPos = parseInt(this.get('selectedVidPos'));
this.set('selectedVidPos', (curArrayPos + 1) % arrayLength);
},
stackHovered() {
this.get('onHoverCallback') (this.get('videos'), this.get('selectedStackIndex'));
}
}
});
video-player.hbs
<video oncanplay={{action 'play'}} looping=true
onended={{action 'ended'}} src={{url}}
class="video-player__video {{highlightedStyle}} {{if playing '' 'video-
player__darken'}}" muted={{muted}} />
video-player.js
import Ember from 'ember';
export default Ember.Component.extend({
url: null,
looping: false,
playing: true,
muted: true,
highlightedStyle: '',
click(event) {
this.get('onClickCallback') (this.get('videoPos'));
event.stopPropagation();
},
mouseEnter() {
this.get('onHoverCallback') (this.get('videoPos'));
},
willClearRender() {
this.set('playingObserver', null);
this.set('urlObserver', null);
},
playingObserver: Ember.observer('playing', function() {
if (this) {
var p = this.get("playing");
var videoElement = this.$().find("video").get(0);
if (videoElement) {
if (p) {
videoElement.play();
}
else {
videoElement.pause();
}
}
else {
console.log("No video element found!");
}
}
}),
urlObserver: Ember.observer('url', function() {
if (this) {
var videoElement = this.$().find("video").get(0);
if (videoElement) {
videoElement.load();
}
else {
console.log("No video element found");
}
}
}),
actions: {
ended() {
if (this.get('looping')) {
this.$().find("video").get(0).play();
console.log("video-player ended");
}
else {
console.log(this.get('videoPos'));
this.get('onEndedCallback') (this.get('videoPos'));
}
},
play() {
if (this.get('playing')) {
this.$().find("video").get(0).play();
}
}
}
});
I can post more code if it would help shed light on the culprit, thanks!
I found the culprit of the lag. The issue was in the parent container, content-area.js, which had a resetTimeout action that was being called incorrectly, which caused the focus to cycle needlessly, resulting in the lag.
Also implemented a switch off in terms of rendering videos to ensure smooth loading from one video to the next in video-stack.js, there are now 2 video objects, A & B, which are fetched and preloaded from the blob object, showing one while the other is hidden. Once the displayed video ends, they swap out, and the next video in the stack is loaded.
video-stack.js
export default Ember.Component.extend({
selectedVidAPos: 0,
selectedVidBPos: 0,
selectedStackIndex: 0,
stackStyle: '',
playerSize: '',
isMuted: true,
showVidA: true,
init() {
...
}
},
videoA: Ember.computed('videos', 'selectedVidAPos', function () {
return this.get('videos')[this.get('selectedVidAPos')];
}),
videoB: Ember.computed('videos', 'selectedVidBPos', function () {
return this.get('videos')[this.get('selectedVidBPos')];
}),
actions: {
stackClicked() {
this.get('onClickCallback') (this.get('videos'), (this.get('showVidA') ? this.get('selectedVidAPos') : this.get('selectedVidBPos')));
},
getNextVideoA() {
let arrayLength = this.get('videos').length;
if (arrayLength === 1) {
return;
}
let curArrayPos = parseInt(this.get('selectedVidAPos'));
this.set('selectedVidAPos', (curArrayPos + 2) % arrayLength);
this.set('showVidA', false);
},
getNextVideoB(){
let arrayLength = this.get('videos').length;
if (arrayLength === 1) {
return;
}
let curArrayPos = parseInt(this.get('selectedVidBPos'));
this.set('selectedVidBPos', (curArrayPos + 2) % arrayLength);
this.set('showVidA', true);
},
stackHovered() {
this.get('onHoverCallback') (this.get('videos'), this.get('selectedStackIndex'));
}
}
});
content-area.js
import Ember from 'ember';
import KeyboardControls from '../mixins/keyboard-controls';
export default Ember.Component.extend(KeyboardControls, {
displayVideoSelect: false,
displayVideoSelectTimeout: null,
displayVideo: false,
video: null,
videoPlaying: false,
keyboard: null,
backgroundVideoPos: 0,
backgroundVideoUrl: null,
backgroundVideoKeys: null,
selectionVideos: [],
stackListData: null,
showVideoSelect: function() {
this.set('displayVideoSelect', true);
this.send('resetTimeout');
},
hideVideoSelect: function() {
this.set('displayVideoSelect', false);
clearTimeout(this.get('displayVideoSelectTimeout'));
},
pauseVideo: function() {
this.set('videoPlaying', !this.get('videoPlaying'));
this.set('displayVideoSelect', !this.get('videoPlaying'));
this.set('focus', this.get('videoPlaying'));
},
select: function() {
this.set('videoPlaying', false);
this.set('focus', false);
this.showVideoSelect();
this.send('resetTimeout');
},
cancel: function() {
this.pauseVideo();
this.send('resetTimeout');
},
goNext: function() {
this.pauseVideo();
this.send('resetTimeout');
},
goPrevious: function() {
this.pauseVideo();
this.send('resetTimeout');
},
updateFocus: function(param) {
if (param) {
this.$().attr('tabindex', 2);
this.$().focus();
}//if
else {
this.$().attr('tabindex', -2);
this.$().blur();
}//else
},
init() {
...
},
click() {
this.set('focus', false);
this.showVideoSelect();
},
actions: {
videoSelected(sender, videoData) {
...
},
videoEnded() {
this.set('focus', false);
this.showVideoSelect();
this.set('displayVideo', false);
},
cycleBackground() {
...
},
cancelPressed() {
this.cancel();
},
resetTimeout() {
let component = this;
clearTimeout(this.get('displayVideoSelectTimeout'));
let timeout = setTimeout(() => {
component.hideVideoSelect();
//This set command was responsible for the lag
component.set('focus', true);
}, this.get('data.config.ui.idle') * 1000);
this.set('displayVideoSelectTimeout', timeout);
}
}
});
I'm trying to update the parents and children checked inputs using bootstrap-treeview, but when I use the method "checkNode", the state of the node doesn't change at all.
var trucks = $('#trucks').treeview({
level: 3,
showCheckbox: true,
selectable: false,
highlightSelected: false,
data: getTree()
}).on('nodeChecked', function (event, node){
var childrenNodes = __getChildren(node);
childrenNodes.forEach(function(n){
$(trucks).treeview('checkNode', [ n.nodeId, { silent: true } ]);
console.log(n.state.checked);
});
});
function __getChildren(node) {
if (node.nodes === undefined) return [];
var childrenNodes = node.nodes;
node.nodes.forEach(function(n) {
childrenNodes = childrenNodes.concat(__getChildren(n));
});
return childrenNodes;
}
The input is checked normally, but console output says the state is "false"
Anyone have an a idea of what I'm doing wrong?
I have object container <div id="dObjectContainer" class="dObjectContainer">
and a widget TreeObject.
$.widget('qs.TreeObject2', {
options: {
oID: '0',
IMGUrl: '../../Content/Images/noIMG.jpg',
Name: 'Name',
IsActive: true,
IsVisible: true
},
_create: function () {
var self = this,
o = self.options,
el = self.element;
el.attr("oID", o.oID);
el.addClass("dObject");
el.addClass("ui-widget-content");
var H1 = $("<h1></h1>").text(o.Name);
el.append(H1);
var img = $("<img></img>");
img.attr("src", o.IMGUrl);
img.attr("alt", o.Name);
el.append(img);
},
DoHide: function (strName, bActive) {
var self = this,
o = self.options;
if (((o.Name.toUpperCase().indexOf(strName.toUpperCase()) > -1) || (strName == "")) && ((o.IsActive) || (bActive == false))) {
if (!o.IsVisible) {
this.element.show();
o.IsVisible = true;
}
}
else {
if (o.IsVisible) {
this.element.hide();
o.IsVisible = false;
}
}
},
destroy: function () {
$.Widget.prototype.destroy.call(this);
}
});
I want to add multiple objects from javascript something like this:
$('#dObjectContainer').append($("<div></div>").TreeObject2({ oID: "1", IMGUrl: '../../Content/Images/StoredImages/Categories/images.jpg', Name: "n1", IsActive: true }));
$('#dObjectContainer').append($("<div></div>").TreeObject2({ oID: "2", Name: "n2", IsActive: false }));
This approach adds two elemets of each object instead of one. - Found reason - for some unknown reson ASP.MVC 3 razor view calls (function ($) {} (jQuery)); twice. First time it calls only view document.ready - second time layoutpage and view document ready.
For other part - how to access their metods:
$('.dObject').TreeObject2("DoHide", strName, bActive);
This works in case there is no more elements having class dObject.
I'm trying to create a javascript class which opens a jQuery dialog and allows the user to select an option then returns the selected value in a callback.
...similar to how the jQuery Alert Dialogs do it.
jPrompt(message, [value, title, callback])
jPrompt('Type something:', 'Prefilled value', 'Prompt Dialog', function(r) {
if( r ) alert('You entered ' + r);
});
Here is a DEMO of what i have so far. But if you notice, the value gets set imidiately, and i want it to wait until the user clicks OK. If the user clicks Cancel, then it should return null or empty string.
Here's my Javascript Class:
var namespace = {};
(namespace.myChooser = function () {
var _dialog = null;
/**
* Function : onButtonCancel
*/
function onButtonCancel() {
_dialog.dialog("close");
return null;
}
/**
* Function : onButtonOK
*/
function onButtonOK() {
_dialog.dialog("close");
}
/**
* Function : Initialize
*/
function Init() {
var dialog_options = {
modal: false,
disabled: false,
resizable: false,
autoOpen: false,
//height: 460,
maxHeight: 300,
zIndex: 500,
stack: true,
title: 'My Chooser',
buttons: { "OK": onButtonOK, "Cancel": onButtonCancel }
};
_dialog = $("#myDialog");
// create dialog.
_dialog.dialog(dialog_options);
_dialog.dialog("open");
}
return {
Choose: function Choose() {
Init();
var myChoice = $("#myOptions").val();
return myChoice;
}
}
}());
And i want to be able to do something like this:
namespace.myChooser.Choose(function (myChoice) {
$("span#myChoice").text(myChoice);
});
SOLUTION:
This is what finally did it:
$(document).ready(function () {
$('#myButton').click(function () {
namespace.myChooser.Choose(function (x) {
console.log(x);
});
});
});
var namespace = {};
(namespace.myChooser = function (callback) {
function _show(callback) {
var dialog_options = {
modal: false,
disabled: false,
resizable: false,
autoOpen: false,
//height: 460,
maxHeight: 300,
zIndex: 500,
stack: true,
title: 'My Chooser',
buttons: {
"OK": function () {
if (callback) callback("OK");
},
"Cancel": function () {
if (callback) callback("Cancel");
}
}
};
_dialog = $("#myDialog");
// create dialog.
_dialog.dialog(dialog_options);
_dialog.dialog("open");
}
return {
Choose: function (callback) {
_show(function (result){
if (callback) callback(result);
});
}
}
}());
Choose: function Choose(cb) {
Init();
var myChoice = $("#myOptions").val();
return cb(myChoice);
}
Should do what you want
Add another variable where you have var _dialog, call it something like var _callback. Then in your Choose function, add a parameter to get the callback function and store it, something like:
Choose: function Choose(f) {
_callback = f;
...
}
Then when you're ready to call the callback function (I presume this is in onButtonOK/onButtonCancel), call the callback function using _callback(parameter);