I am trying to use a dom repeat template with a custom element wrapping highcharts (https://github.com/avdaredevil/highcharts-chart). It is mostly working except when the data gets changed the charts do not reflect it.
The dom-repeat template:
<template id="scenarioCharts" is="dom-repeat" items="{{chartOptions}}" as="chartOption">
<highcharts-chart highchart-options="{{chartOption}}" />
</template>
The Polymer code to build chartOptions (which is a property with notify true):
observers: [
'buildChartOptions(scenarios)',
],
buildChartOptions(scenarios) {
var i = 0;
this.set('chartOptions', []);
for (i = 0; i < scenarios.length; i += 1) {
this.push('chartOptions', buildCustomChartOptions(scenarios[i]));
}
},
If I remove the line this.set('chartOptions', []); the dom-repeat keeps the old ones and adds the new charts. I have also tried a lot of different things with splices and notifySplices but have had no luck producing the desired result which is the old charts being replaced by the new ones.
Thanks
I would refer to the same bug mentioned by tony19.
Your code seems to be a good workaround, though I'd probably create a local array, populate it and set it again, like this:
buildChartOptions(scenarios) {
var newArray = [];
for (var i = 0; i < scenarios.length; i += 1) {
newArray.push(buildCustomChartOptions(scenarios[i]));
}
this.set('chartOptions', newArray);
}
Another alternative would be to push your changes to the array and then force the dom-repeat to render, by doing a this.$.scenarioCharts.render();. You'd need to try it though, I'm not sure it would work:
buildChartOptions(scenarios) {
for (var i = 0; i < scenarios.length; i += 1) {
this.push('chartOptions',buildCustomChartOptions(scenarios[i]));
}
this.$.scenarioCharts.render();
}
Additionally, even though it does not concern your question, I would recomend declaring a function in polymer like this:
buildChartOptions: function(scenarios) { }
instead of
buildChartOptions(scenarions) { }
I sometimes forget and do the same you did, and the result is compatibility issues with Internet Explorer or Firefox... However if you do as suggested, everything will work fine.
Related
I am working on a Preact-CLI project with a Preact-Router and it works fine on the localhost. But the production doesn't work well after the build.
I have created a one page object which gets its content dynamically from a JSON file (inside the project not external). So I've loaded the same page object 2 times for each different page.
I get the page url (using this.props.permalink) and compare it with the JSONObject.title. If they are the same I want to get the corresponding JSON content to display it on the corrrct page. Works like a charm on localhost, but not in production.
Issue:
Somehow all pages get the content of the first JSON element. First I thought it was a server issue but I was wrong. The builded files are wrong after the prerendering/build. So the prerendered html of page B contains the content of the prerendered page A.
My guess is that during the build this.props.permalink doesn't work. How should I handle this?
Additional info:
I use the prerender function but not the service worker for the build.
Thanks!
UPDATE:
I have rewritten the function. I guessed I needed to set the dynamic content through a loop, so that during the build the compiler loops through it and is able to prerender all the pages.
The iteration and setting the state works, but only the final element of the PrerenderUrls array gets stored. So now all pages gets the JSON content of the first element.
componentWillMount() {
for (var i = 0; i <= PrerenderUrls.length; i++) {
// the code you're looking for
let removeDash = new RegExp("-")
var needle = PrerenderUrls[i].title
var needle1 = needle.replace(removeDash, " ")
alert("1")
// iterate over each element in the array
if (needle1 != "Homepage") {
for (var x = 0; x < Data.length; x++) {
// look for the entry with a matching `code` value
let removeDash = new RegExp("-")
var nodash = Data[x].title.replace(removeDash, " ")
var nocaps = nodash.toLowerCase()
if (nocaps == needle1) {
alert("needle2: "+ needle1 + " nocaps: " + nocaps)
//alert("data "+ Data[x].title)
this.setState({
pageTitle: Data[x].title,
descShort: Data[x].descShort,
description: Data[x].desc,
img: Data[x].img
})
alert("state "+ this.state.pageTitle)
}
}
}
}
From your description it seems you have a standard Javascript closure problem. I noticed you use both let and var. If let is supported, use it instead of var. It will automagically solve your closure issues, because let creates variables with the block scope, instead of a function scope. Otherwise, you can try to replicate how let does it under the hood - throw the variable to the callback function. Something in the lines of:
// ...
for (var x = 0; x < Data.length; x++) {
try { throw x }
catch(iterator) {
this.setState({
pageTitle: Data[iterator].title
});
}
}
PS. It is very difficult to follow your code, when it is so specific to your functionality. You could simplify it, and focus on the troubling issue. Most of the code you provided is not relevant to your problem, but makes us going through it anyway.
Introduction
I coded a portfolio website for a friend of mine as a university project.
I started to learn Vue.js and started to dive into JavaScript in general.
http://janpzimmermann.com
In some cases I'm still struggling with all the new stuff. Therefore, I'm mixing Vue.js with jQuery and JavaScript. I know this isn't best practice.
But after spending years and years with mostly html and css (and sometimes a little PHP) some things are still new to me. ;)
The Problem
I created a gallery grid (the content is loaded via Vue) and wanted to be able to filter the content via navigation.
Therefore, I came across the following method:
https://www.w3schools.com/howto/howto_js_filter_elements.asp
/* content filter */
filterSelection("all");
function filterSelection(c) {
var x, i;
x = document.getElementsByClassName("content-filter");
if (c == "all") c = "";
// Add the "view" class (display:block) to the filtered elements, and remove the "view" class from the elements that are not selected
for (i = 0; i < x.length; i++) {
w3RemoveClass(x[i], "view");
if (x[i].className.indexOf(c) > -1) w3AddClass(x[i], "view");
}
}
// Show filtered elements
function w3AddClass(element, name) {
var i, arr1, arr2;
arr1 = element.className.split(" ");
arr2 = name.split(" ");
for (i = 0; i < arr2.length; i++) {
if (arr1.indexOf(arr2[i]) == -1) {
element.className += " " + arr2[i];
}
}
}
// Hide elements that are not selected
function w3RemoveClass(element, name) {
var i, arr1, arr2;
arr1 = element.className.split(" ");
arr2 = name.split(" ");
for (i = 0; i < arr2.length; i++) {
while (arr1.indexOf(arr2[i]) > -1) {
arr1.splice(arr1.indexOf(arr2[i]), 1);
}
}
element.className = arr1.join(" ");
}
Unfortunately, there seems to be a bug.
When I open a project, don't close it with the close button and navigate to a new category and open a project there, the project which was opened before is added to the DOM again (even if it doesn't belong to the category!).
I neither couldn't find the bug, yet. Nor I was able to be sure it's not a fault of Vue.
But I tried to replace the JavaScript filter with a jQuery one (this one worked with data-attributes), sadly this wasn't working for me. As I just could add one attribute per item. But sometimes a project belongs to more than one category. (this one: https://jsfiddle.net/k5g6wcw3/21/)
// Variable
var posts = $('.post');
posts.hide();
// Click function
$( ".sort" ).click(function() {
// Get data of category
var customType = $( this ).data('filter'); // category
console.log(customType);
console.log(posts.length); // Length of articles
posts
.hide()
.filter(function () {
return $(this).data('cat') === customType;
})
.show();
});
// All
$( "#showAll" ).click(function() {
$( ".post" ).show();
});
Further thoughts
I know this should be also possible to do with vue routes and maybe vuex, but couldn't find a way how to do it which was understandable to me.
thanks
Mixing Vue and jQuery like this is going to make things much, much, much more difficult than they need to be. The problem you ran into with your first filter was because Vue didn't know about the DOM modifications your javascript filter was doing, so overwrote them on its next update. You're going to have exactly the same problem with the jQuery filter, if you get it working.
So don't do that.
Right now you're letting Vue draw a full list of items, then trying to crawl through the DOM after the fact adding data attributes and hiding the elements you want filtered out. This is a lot of extra work (for both you and the browser), and will fail whenever Vue does a redraw (because it will blow away the changes you made externally.)
Instead, put the attributes in the data you're feeding to Vue, and let Vue filter it before it draws the DOM. Which is a thing that is pretty simple to do in Vue.
(You had mentioned the need for multiple categories per project; here's a quick example of a computed property for that:
data: {
currentFilter: 'photo', // set this from a route param, or a v-model, or whatever
projects: [
{name: "Project one", categories: ['photo', 'book']},
{name: "Project two", categories: ['website']},
{name: "Project 3", categories: ['photo']}
// ...
]
},
computed: {
filteredProjects() {
return this.projects.filter(project => {
return project.categories.indexOf(this.currentFilter) > -1
})
}
}
Have your template v-for over filteredProjects instead of projects, and you're done. Whenever data.currentFilter changes, the list will redraw, filtered by the new value, automatically.)
It's possible to use jQuery from within Vue, but it requires a pretty good understanding of what the framework is doing so you don't wind up creating conflicts between it and your external code. (And I've yet to find a case where it wasn't simpler to just rewrite the jQuery thing as a Vue component anyway.) The same is true of other modern frameworks like React or Angular; these all work on a very different model than the DOM-first strategy jQuery tends to fall back on. Especially when learning, you'll have a much easier time of it if you stick to just one framework at a time instead of trying to mix things together.
I am working on a reporting related project, where I need to build lot of reports rendered using KO. All data pulled using AJAX and the model is updated. Currently I am writing tons of js functions to map the models. Something like:
function modelx(child) {
var self = this;
self.Name = ko.observable(child.Name);
self.Relation = ko.observable(child.Relation);
// hundred other properties
};
function modely(child) {
var self = this;
self.Age = ko.observable(child.Age);
self.Relation = ko.observable(child.Relation);
// hundred other properties
};
and after AJAX call, I am filling the observable arrays
for (var i = 0; i < jsn.length; i++)
{
VM.modelxlist().push(new modelx(jsn[i]));
}
for (var i = 0; i < jsn1.length; i++)
{
VM.modelylist().push(new modely(jsn1[i]));
}
Is there any way to avoid the definition of modelx, modely,... such that the model is automatically built without loosing the benefits of this approach while using in HTML? Of course there could be a corner case where I may not get a specific property from server, which I should check on the server side.
Also, at times I may need to add additional computed observables (just to be more flexible)
Why don't you use knockout mapping plugin:
http://knockoutjs.com/documentation/plugins-mapping.html
You would then have something like:
var modelxInstance= ko.mapping.fromJS(child);
There are a few mapping plugins for knockout, the one i like the most is actually this one:
https://github.com/LucasLorentz/knockout.mapper
And the reason is that it is more configurable and it is faster.
I think this is what you want..
With ko.mapping.fromJS method u can automatically observe all the properties from your object..
Take some time to read about that..
I'm trying to use infinite-scroll to lazy load images. I'm getting the following error when it's called though:
TypeError: undefined is not a function
at handler (http://onfilm.us/ng-infinite-scroll.js:31:34)
Here's a very watered down look of what I have thus far.
function tagsController($scope) {
$scope.handleClick = function(tags) {
// Parse Tags
$scope.finished_tags = parsed_data;
};
$scope.$emit( 'handleEmit', { tags = $scope.finished_tags; });
};
function imagesController($scope,$http) {
var rows_per = 5;
$scope.$on('handleBroadcast', function(event, args) {
// Sort the images here, put them in matrix
// Example: matrix[row_number] = { picture1, picture2, picture3 }
$scope.data = matrix;
$scope.loadMore();
};
$scope.loadMore() = function() {
var last = $scope.images.length;
for ( var i = 0; i < rows_per; i++ ) {
$scope.images[last + i] = new Array();
$scope.images[last + i] = $scope.data[last + i].slice( 0 );
}
}
}
The rough idea is that the page loads the first time (w/ no tags) and get images from a PHP script. All of them. They are stored, and loadMore() is called which will populate $scope.images with 5 rows of images. It does, and they are loaded.
The line in that script is accessing $window.height and $window.scrollup. I'm still pretty green w/ Javascript, so feel free to lambast me if I'm doing something horribly wrong.
This is the broken version I'm testing with:
http://onfilm.us/test.html
Here is a version before the lazy loading was implemented, if seeing how the tags work will help. I don't think that's the issue here though.
http://onfilm.us/image_index.html
EDIT: I do think this is a problem w/ the ng-infinite-scroll.js script. The error is on line 31 (of version 1.0.0). It's telling me:
TypeError: undefined is not a function
It doesn't like $window apparently.
My JS Kung Fu is not really equipped to say why. YOu can see a literal copy/paste job from the simple demo here (with the error) onfilm.us/scroll2.html
By refering your site, It appears at first instance that your HTML-markup is not appropriate. You should move infinite-scroll to the parent of ng-repeat directive so that it will not make overlapping calls for each row generated. Please visit http://binarymuse.github.io/ngInfiniteScroll/demo_basic.html
Due to data being loaded via AJAX I need my data to be updated when data arrives.
On page load I collect projects in database. Then load data for Tasks and Tags depending on which project is selected (self.SelectedProject).
self.Projects = ko.observableArray();
self.Tasks = ko.observableArray();
self.Tags = ko.observableArray();
self.SelectedProject = ko.observable(); // Chosen Project-object...
For initialization I load data for the first Project:
self.SelectedProject(self.Projects()[0]); // Choose first returned Project...
Then I go on to populate my tag-helping arrays:
ko.computed(function () {
// must be ko.computed as else will not update when data arrives for Tags and Tasks (which are likely to be empty at load time)...
// Empty projectAvailableTags before refill...
self.SelectedProject().projectAvailableTags([]);
// First populate current project's "projectAvailableTags"-array with values...
for (var j = 0, jlen = self.Tags().length; j < jlen; j++) {
self.SelectedProject().projectAvailableTags().push(self.Tags()[j].TagName());
}
for (var i = 0, ilen = self.Tasks().length; i < ilen; i++) {
//---- Populate each TaskTag-array with Tags...
for (var j = 0, jlen = self.Tags().length; j < jlen; j++) {
if (self.Tags()[j].TagTaskId() === self.Tasks()[i].TaskId) {
self.Task()[i].TaskTags.push(self.Tags()[j]);
// Populate the different tag-Arrays...
var tagtype = self.Tags()[j].TagType;
switch (tagtype()) {
case 0: self.Tasks()[i].Location().push(self.Tags()[j].TagName()); break;
case 1: self.Tasks()[i].Manager().push(self.Tags()[j].TagName()); break;
case 2: self.Tasks()[i].Employee().push(self.Tags()[j].TagName()); break;
}
}
}
};
});
This probably look strange and maybe I am doing it unnecessarily complicated.
I use http://aehlke.github.com/tag-it/ as tag manager and it needs an array with TagNames only. Thus I haven´t figured out how to use the Tags()-array directly although I´d like that.
Tasks are presented in an accordion, and I want the Task-tags to be applied in the content panel, while I use my Project-tags as the tagSource for autocomplete-functionality...
But I cannot figure out why my tags are applied 2 times with the ko.computed while they aren´t applied unless I reselect the project without it.
I think you're kind of missing the point of computed observables. The only real distinction between computed observables and a regular function is that you can bind to a computed observable and rely on it to auto-update any time one of its components changes.
The example in the knockout documentation for computed observables uses First/Last name, which is a good example.
Based on this, it's really not a good idea to update the dependency of a computed within that computed itself. In earlier version of knockout this would have actually create an infinite circular reference.
I assume the computed is running twice because Tags and Tasks are both receiving new data, which is triggering an update (but only one update because of the safeguards built into knockout).
A better option would be to subscribe to SelectedProject. Then every time that observable changes you can re-rack all your arrays.
self.SelectedProject.subscribe(function(newValue) {
<load your arrays here>
});