calling render() after teardown() does not display list data - javascript

I have list of menu options, and each menu item has it's own Ractive instance with different template but same shared data. When each selection is changed I am calling teardown() on rendered view instance and render(domElement) on current selection's Ractive instance.
An example Instance is like below, and all follow the same structure.
var View = new Ractive({
template: '#contacts',
data: {
name: 'Contacts',
contacts : dummyData // array data
}
});
And I render them like below
var isRendered = false;
channel.subscribe("menu", function(msg) {
if(msg === "contacts") {
contentHolder.innerHTML = "";
View.render(contentHolder);
isRendered = true;
} else {
if(isRendered) {
View.teardown();
isRendered = false;
console.log(View.get('contacts')); // Here I can see the data.
}
}
});
In first render() call view is rendered as expected, but after calling teardown(), again if I call render() it does not render contacts list data and only displays name property, but was rendered on initial call.
Please help me to fix this.

Just for reference, the question was answered on GitHub
teardown() is a non-reversible call that completely destroys the ractive instance. What you want is detach() function, which will remove the ractive instance from the DOM but not destroy it. You can use it later by calling insert().

Related

Vuejs3/Vuex4 conditional render on promise fulfillment

I have custom objects for holding child objects full of data. The child objects are initiated with null values for all their properties, so the objects can be referenced and their properties filled from remote sources. This creates a lazy-loading setup.
This code is going to be extremely trimmed down, but everything relevant should be here:
class Collection extends Object {
constructor(){
this.loaded = false;
var allLoaders = [];
var loaderPropmises = [];
var resolver;
const $this = this;
var trackLoaders = function(){
$this.loaded = false;
loaderPromises.push(Promise.all(allLoaders).then(() => {
//... irrelevant logic in here to ensure only the latest promise sets loaded to true
$this.loaded = true; //This is getting called where I expect
resolver();
}));
}
//hook for outside things to watch the promise if they want
this.loader = new Promise((resolve) => {
//this only gets resolved once, which is fine
resolver = resolve;
});
//... bunch of code around adding child objects, but the important part:
this.add(child){
this[child.id] = child;
this.allLoaders.push(child.loader);
trackLoaders();
}
}
}
The child then looks like:
class Child extends Object {
constructor(){
this.loaded = false;
var resolver;
const $this = this;
this.loader = new Promise((resolve) => {
resolver = resolve;
}).then((){
$this.loaded = true;
});
this.populate(data){
//bunch of stuff to set data to properties on this object
resolver();
}
}
}
In Vuex 4 I have these Collections as properties on an "AppData" object in the store:
const store = createStore({
state: function(){
AppData: {}
},
mutations: {
setupCollection(state, name){
if (!Object.hasOwnProperty.call(state.AppData, name){
state.AppData[name] = new Collection();
}
}
},
actions: {
//this is called on each row of data returned from an Axios call
add (context, {name, data}){
context.state.AppData[name][data.id].populate(data);
}
}
});
The idea is that whenever a Child is added to a Collection, the collection loaded property will be false until all the Child loader promises resolve. This all executes perfectly... Except that the loaded bools aren't reactive.
Right now, I have a Promise.all in each component's Created function that flags the component as "loaded" once all the objects needed for the component have had their "loader" promises resolved. This absolutely works, but isn't ideal as different data will be available at different times, and there are sometimes hundreds or more of these classes on screen at once. What I'm trying to accomplish is:
<div v-if="!myCollection.loaded">
Loading...
</div>
<div v-else>
Show the data I want here {{myCollection.property}}
</div>
So I have two thoughts on overcoming this, either of which would be great:
VueJS3 no longer has a need for Vue.set(), because Proxies. How would I make the loaded bools here reactive then? Or more specifically, what am I doing that prevents this from working?
Alternatively, is there a practical way to use the loader promise directly in a template?
It looks like Vue's ref is what I needed:
this.loaded = ref(false);
This works, at least on the Child class. I have some sort of circular referencing issue going on and haven't been able to test on the Collection class yes, but it should work the same.

Vuejs template not updating applying filter to property

I have a simple template that iterates over some items:
<template v-for="card in filteredCards">
filteredCards are a property I am using to filter some results by clicking a simple html link. In my Vue component I have this:
data = {
cards: '',
filteredCards: ''
}
cards is the original data coming via an ajax request - and filteredCards is what I'm actually iterating over.
The problem becomes when I do any kind of update with a filter - the template is not reflecting the filtered array. Here is how I'm filtering:
this.filteredCards = this.cards.filter(function (item)
{
return item.Data.event_type.match('something_test');
});
In devtools I can see that the array has been updated to only a single item - however the template never updates and leaves all the results showing. If I call something that actually mutates the array though like reverse - the template updates just fine. Is there something I need to do in order to force an update after filtering the array?
I've updated a bit to reflect using a custom filter. I'm still running into the same problem. In devtools I see that the filterKey is being updated from an event broadcasted from the parent instance. But nothing is being updated in the template.
var $data = {
cards: [],
filterKey: '',
loading: true
};
Vue.component('cards', {
template: '#card-template',
data: function()
{
return $data;
},
events: {
'updateFilterKey': function(key)
{
this.filterKey = key;
}
}
});
Vue.filter('onlyMatching', function(cards)
{
var $this = this;
return cards.filter(function(item)
{
return item.Data.event_type.match($this.$data.filterKey);
});
});
The code that initially gets the data is just a simple ajax call:
var getFeed = function($url)
{
$.get($url, function(response)
{
$data.loading = false;
$data.cards = response;
}).fail(function()
{
$data.loading = false;
});
};
The strange thing is with this current code - when I click back and forth between sending different keys the actual array items are being duplicated in my template over and over when I click the "all items" which sets the filterKey to an empty string.
What you're doing is absolutely backwards.
You actually want to loop the original array but apply a filter on it.
<template v-for="card in cards | filterBy onlyMatching">
Then, in your code, create a custom filter:
Vue.filter('onlyMatching', function (cards) {
return infoBlocs.filter(function(item) {
return item.Data.event_type.match('something_test');
});
})
This should totally work. Now, whenever the array is changed in any way, the filter will get triggered and the list will be re-rendered.
Come to find out the filtering was working all along. There's an issue with the conditional templating. I'll ask that in another question.

Using a kendo observable property in multiple viewModels

In a Kendo app using the Kendo MVVM framework: I have a "global" viewModel which is information that is common to all parts of the app - e.g. the UserState, which has a property isLoggedIn.
Many different Views and ViewModels access the userState object (from what I can see, 1 View is bound to 1 ViewModel in Kendo).
For example, my home page might show the Login button if they are not authenticated. Then all the other screens behave differently once you are logged in, so each ViewModel needs to reference the UserState object. However, if any of them change it then all other Views should update as I used a Kendo Observable object. This does not seem to work.
I set up a simple example here to illustrate the problem: http://jsfiddle.net/rodneyjoyce/uz7ph/11
var app = new kendo.mobile.Application();
userState = (function ()
{
var userStateViewModel = kendo.observable({
isLoggedIn: false
});
function loginUser()
{
userStateViewModel.set("isLoggedIn", true);
alert('Logged in');
};
return {
userStateViewModel: userStateViewModel,
loginUser: loginUser
}
})();
var viewModel1 = kendo.observable({
label: 'ViewModel1',
isLoggedInVM1: function() {
return userState.userStateViewModel.get("isLoggedIn");
},
logIn: function ()
{
//when calling LoginUser from here, the binding is not updated, even though the value is changed (true)
userState.loginUser();
alert('After Login viewModel1.isLoggedInVM1() = ' + viewModel1.isLoggedInVM1() + ' but the binding has not updated');
}
});
alert('Value onLoad = ' + viewModel1.isLoggedInVM1());
//If you uncomment this and call LoginUser from here then afterwards the binding changes to true, but not if you call it from within ViewModel1
//userState.loginUser();
kendo.bind($("#testForm"), viewModel1);
When I call userState.loginUser() to change the value of isLoggedIn in userStateViewModel it does not update. Run and click on the button to see the problem - the binding does not reflect the updated value (but the alert box does). Any help appreciated, thank you.
Note: This is en extension of an earlier question which got me a bit further.
The problem is that userState is a simple object, not an ObservableObject. Because of this, the change event of the userStateViewmodel observable does not trigger the change event for viewmodel1 and the view doesn't update.
You can remedy this by making userState a property of viewModel1, so it is wrapped in an observable (or you could wrap your return object in the IIFE in an observable):
var viewModel1 = kendo.observable({
label: 'ViewModel1',
userState: userState,
isLoggedInVM1: function() {
return userState.userStateViewModel.get("isLoggedIn");
},
logIn: function ()
{
userState.loginUser();
}
});
Take a look at this demo; try commenting the userState property and you'll see the difference.

ember.js .find() only works when called 2nd time

Main goal: Using .find() to access a model other than the one available in the current controller -in order to compare data from the current controller's model with a piece of data from a 'foreign' controller's model.
What triggers the comparison:
I have a button inside a template with {{ action "isResponse"}}. This template's controller has an isResponse : function() {...}
The problem I have: The action is fired every time I click the button, but App.Answer.find() only returns content after the 2nd click. I'm wondering if this is because the Answer model hasn't loaded, but am unsure how to properly set up an observer for isLoaded in my example (if that is even the issue)
So how come App.Answer.find() returns empty the first time it's called??
App.ChoiceController = Ember.ObjectController.extend({
chosen: false,
isResponse: function() {
// successfully returns what I want from this controller's model
var questionId = this.get('question.id')
// gets DS.RecordArray of the model i'd like to compare with
var answers = App.Answer.find()
// filter to get a result that matches this.get('question.id')
var answer = answers.filter(function(ans) {
// returns all entries that match
if(ans.get('question.id') == questionId) { return true }
}, 'answers.isLoaded'); // this observer doesn't seem to hurt or help
// get the final value I need
var choice = answer.mapProperty('choice.id')
// if choice array is not empty, (should only have 1 element anyways)
if(!choice) {
this.set('chosen', choice[0]);
} else {
this.set('chosen', false);
}
}
})
Here are the models involved. Both include DS.belongsTo attributes
App.Choice = DS.Model.extend({
"question" : DS.belongsTo('App.Question')
})
App.Answer = DS.Model.extend({
"question" : DS.belongsTo('App.Question')
"choice" : DS.belongsTo('App.Choice')
})
App.Question = DS.Model.extend({
})
EDIT
Here is jsfiddle showing the behavior. Make sure to open your browser console to notice that each button requires 2 clicks for action isResponse to function properly. http://jsfiddle.net/iceking1624/QMBwe/
After reading your comment I've retought a solution to your problem and one possible way might be that you can define a AnswerController of type ArrayController (since it's for a collection of answers) and then setup this controller in your ApplicationRoute's setupController hook.
Main goal: Using .find() to access a model other than the one available in the current controller -in order to compare data from the current controller's model with a piece of data from a 'foreign' controller's model.
Later on you can then require access to the AnswerController's data using the needs API with needs:['answers'] from inside whatever controller that needs access to the answers collection, and finally have access to the data with this.get('controllers.answer'). You can find here more info on the needs API.
See here a possible solution that works correctly, displaying the right choice already on the 1st click:
App.AnswerController = Ember.ArrayController.extend({});
App.ApplicationRoute = Ember.Route.extend({
setupController: function(controller, model) {
this.controllerFor('answer').set('content', App.Answer.find());
}
});
App.ChoiceController = Ember.ObjectController.extend({
needs: ['answer'],
chosen: false,
isResponse: function() {
var questionId = this.get('question.id');
var answers = this.get('controllers.answer');
var answer = answers.content.filter(function(ans) {
if(ans.get('question.id') == questionId) { return true }
}
var choice = answer.mapProperty('choice.id');
if(!choice) {
this.set('chosen', choice[0]);
} else {
this.set('chosen', false);
}
}
});
And here a working fiddle.
Hope it helps.

ExtJS refreshing a view from the controller

I have the following controller in ExtJs:
Ext.define('FileBrowser.controller.BrowserController', {
extend: 'Ext.app.Controller',
views: ['browser.tree_dir', 'browser.grid_file'],
stores: ['store_dir', 'store_file'],
init: function () {
this.control({
'window > tree_dir': {
itemclick: {
fn: function (view, record, item, index, event) {
if (record.isLeaf() == false) {
Ext.getStore('store_file').load({
params: {
dir: record.data.id
}
});
var parentOfCurrentFiles = record.data.id
nodeId = record.data.id;
htmlId = item.id;
var grid_view = this.getView('browser.grid_file');
var grid_view_v = grid_view.getView();
grid_view_v.refresh();
}
}
}
}
});
},
onPanelRendered: function () {
console.log('The panel was rendered');
}
});
If you notice under 'itemclick' I am trying to refresh one of my views, my approach is not working. Can anyone explain to me how I can refresh the view? Thank you.
Replace var grid_view= this.getView('browser.grid_file'); with var grid_view= this.getView('browser.grid_file').create(); to get a real instance (as I already told you, getView() only return the view config, not a instance!) or if you have already created that grid and only one instance exist use the xtype along with a component query to receive it var grid_view=Ext.ComponentQuery('grid_file')[0]
Now to the refresh()
Basically you never need to call this method cause your grid is bound to a store and any change made on this store is directly reflected to your grid.
I would also recommend you to store view instances when creating them instead of using queries or directly use the ref property and let ExtJS do the work for you. The last one will the best solution you I guess... Take a look at ref's within the API examples and give it a try.
So what you are trying to do is, load the store and have the data reflect once you refresh the grid_view...?
In that case, you haven't done a setStore() to the grid, or if you have done that elsewhere, you are't doing a setData() to the store. Also you should call the refresh on the grid.

Categories