how to add record to array - javascript

I'm beginner in Emberjs, so i need to pass selected item in list to basket.
I have route catalog
<div class="flexbox">
{{left-menu-bar}}
<div class="main">
{{side-basket items=items}}
<div class="catalog-container">
<div class="container-inner">
{{#list-filter filter=(action 'filterByName') as |resultItems i|}}
<ul class="responsive-table">
<li class="table-header" >
<div class="col col-1">Наименование</div>
<div class="col col-2 radio-group">Год<i class="fas fa-angle-up angle angle-left radio" note="up" {{action 'sortColumn' value="target.note"}}></i><i class="fas fa-angle-down angle radio" data-value="down"></i></div>
<div class="col col-3">Количество<i class="fas fa-angle-up angle angle-left radio"></i><i class="fas fa-angle-down angle"></i></div>
<div class="col col-4">Цена<i class="fas fa-angle-up angle angle-left"></i><i class="fas fa-angle-down angle"></i></div>
<div class="col col-5">Примечание</div>
</li>
{{#each resultItems as |itemUnit|}}
{{item-list item=itemUnit gotItem=(action 'getBasketItem')}}
{{/each}}
</ul>
{{/list-filter}}
</div>
</div>
</div>
catalog.js controller
export default Controller.extend({
items: [],
actions: {
filterByName(param) {
if (param !== '') {
return this.get('store').query('item', {name: param})
}
}
getBasketItem(param){
if (param !== '') {
var item = this.get('store').query('item', {name: param});
//how to add item in items to use in side-basket
}
}
}
});
component item-list.hbs
<li class="table-row hvr-grow" {{action 'handleItem' item.name}}>
{{yield result}}
<div class="col col-1" data-label="Наименование">{{item.name}}</div>
<div class="col col-2" data-label="Год">{{item.year}}</div>
<div class="col col-3" data-label="Количество">{{item.quantity}}</div>
<div class="col col-4" data-label="Цена">{{item.cost}}</div>
<div class="col col-5" data-label="Примечание">{{item.info}}</div>
</li>
item-list.js
export default Ember.Component.extend({
selectedIndex : false,
actions: {
handleItem(param) {
let handledItem = this.get('gotItem');
handledItem(param);
}
}
});
and side-basket component with nested basket-list component
scheme
test
test with manual writing
how to realise this transfer?

In the long-run, you will find it easier if you move your data fetching to your route rather than the controller and just use query params in the controller. If you make this change, when you set your filter you can update the query params, which will in turn cause the router to refresh the model. The Ember docs explain this quite well on this page:
https://guides.emberjs.com/release/routing/query-params/
and you also need to look at this section:
https://guides.emberjs.com/release/routing/query-params/#toc_opting-into-a-full-transition
Note: this.get('store').query('item', {name: param}) in getBasketItem is returning a promise. If you change to fetching your data in the route using query params, you will be able to access your data via this.get('model') in your component and reference model in your route template - both of which will be the resolved data rather than a promise.
That should then simplify your code and make it easier to work with actual items in the controller and component. Rather than needing to fetch the item from the store in getBasketItem, you should be able to pass the item as a parameter and push it onto your items property with something like this.get('items').pushObject(selectedItem).

Related

Vue CLI - TypeError: Cannot read properties of undefined (reading '1')

I'm beginner in VueJS and hoping for your help.
I'm trying to create Weather forecast app based on OpenWeatherMap API.
The concept is such that:
Enter some location to input on homepage and click to search button. (in my code it's a component Search.vue)
Redirecting to another page and show results - current weather and forecast for next 6 days. (component Weather.vue)
I created function with two consistent fetch calls. First fetch taking entered input query and return needed data from Current Weather Data API. After that, function run second fetch to One Call API based on latitude longitude from first fetch.
Everything is working and showing fine, but i don't undestand why i have an error Uncaught (in promise) TypeError: Cannot read properties of undefined (reading '1') in console:
Сan someone know how to fix this error?
My Search.vue (homepage) component:
<template>
<div class="row">
<div class="search-col col">
<div class="search-box">
<input
type="text"
class="search-bar"
placeholder="Location"
v-model="query">
<router-link :to="{name: 'DetailedWeather', params: { query: query }}" class="btn-search">
<i class="fas fa-search"></i>
</router-link>
</div>
</div>
</div>
</template>
My Weather.vue (weather results showing page) component:
<template>
<div class="row" v-if="typeof weather.main != 'undefined'">
<div class="weather-col col">
<div class="weather-app">
<div class="weather-box">
<div class="weather-top-info">
<div class="clouds-level"><span class="icon"><i class="fas fa-cloud"></i></span> {{ weather.clouds.all }}%</div>
<div class="humidity"><span class="icon"><i class="fas fa-tint"></i></span> {{ weather.main.humidity }}%</div>
</div>
<div class="weather-main-info">
<div class="temp-box">
<div class="temp-main">{{ Math.round(weather.main.temp) }}</div>
<div class="temp-inner-box">
<div class="temp-sign">°C</div>
<div class="temp-max"><span class="icon"><i class="fas fa-long-arrow-alt-up"></i></span> {{ Math.round(weather.main.temp_max) }}°</div>
<div class="temp-min"><span class="icon"><i class="fas fa-long-arrow-alt-down"></i></span> {{ Math.round(weather.main.temp_min) }}°</div>
</div>
</div>
<div class="weather-desc">{{ weather.weather[0].description }}</div>
<div class="weather-icon">
<img :src="'http://openweathermap.org/img/wn/'+ weather.weather[0].icon +'#4x.png'">
</div>
</div>
<div class="weather-extra-info">
<div>Feels like: <span>{{ Math.round(weather.main.feels_like) }}°C</span></div>
<div>Sunrise: <span>{{ weather.sys.sunrise }}</span></div>
<div>Sunset: <span>{{ weather.sys.sunset }}</span></div>
</div>
</div>
</div>
</div>
<div class="forecast-col col">
<div class="row">
<div class="forecast-item-col col" v-for="day in forecastDays" :key="day">
<div class="forecast-box">
<div class="forecast-date">{{ forecast.daily[day].dt }}</div>
<div class="forecast-temp">{{ Math.round(forecast.daily[day].temp.day) }}°C</div>
<div class="forecast-icon"><img :src="'http://openweathermap.org/img/wn/'+ forecast.daily[day].weather[0].icon +'#2x.png'"></div>
</div>
</div>
</div>
</div>
</div>
<div class="row">
<div class="actions-col col">
<router-link :to="{name: 'Search'}" class="btn btn-default">
Back to search
</router-link>
</div>
</div>
</template>
<script>
export default {
name: 'Weather',
props: ['query'], //getting from homepage
data() {
return {
api_key:'b7fe640e9a244244a6f806f3a6cbf5fc',
url_base:'https://api.openweathermap.org/data/2.5/',
forecastDays: 6,
weather: {},
forecast: {}
}
},
methods: {
fetchWeather(){
// first call
fetch(`${this.url_base}weather?q=${this.query}&units=metric&appid=${this.api_key}`)
.then(response =>{ return response.json() }).then(this.setResults);
},
setResults(results){
this.weather = results;
// consistent second call
fetch(`${this.url_base}onecall?lat=${results.coord.lat}&lon=${results.coord.lon}&exclude=current,minutely,hourly,alerts&units=metric&appid=${this.api_key}`)
.then(data =>{ return data.json() }).then(this.setForecast);
},
setForecast(results){
this.forecast = results
},
},
created() {
this.fetchWeather();
}
</script>
My router/index.js file:
import { createRouter, createWebHashHistory } from 'vue-router'
import Search from '../components/Search.vue'
import Weather from '../components/Weather.vue'
const routes = [
{
path: '/',
name: 'Search',
component: Search
},
{
path: '/detailed-weather',
name: 'DetailedWeather',
component: Weather,
props: true
}
]
const router = createRouter({
history: createWebHashHistory(),
routes
})
export default router
From what I guess (given the code and the error), there might be an issue with the objects you are receiving from the API.
The error message suggests you are trying to read something from an array at a specific index that is undefined.
The only occurrence in your code that could cause this error is from the template, where you are reading, for example:
{{ forecast.daily[day].dt }}
{{ Math.round(forecast.daily[day].temp.day) }}
I can't tell exactly which one is it, but try to double check the shape of the objects you are working with.

How to target an item inside ngFor with introjs

I need to use introjs to tour new features on an angular 6 app.
Inside the tour some step target items in ngFor in a child component of the component where I start introjs. The library is able to target child component html elements but seems to not be able to target element inside ngFor. The usage I want is simple I have a li that represent a card item I loop on as :
<div *ngFor="let source of sourcesDisplay; let idx = index" class="col-lg-5ths col-md-4 col-sm-6 col-xs-12">
<!-- item -->
<div class="item blue-light">
<div class="header">
<div class="categories">
<div class="truncat">{{source.categories.length > 0? source.categories : 'NO CATEGORY'}}</div>
</div>
<a routerLink="{{ organization?.name | routes: 'sources' : source.id }}" class="title">
<div class="truncat">{{source.name}}</div>
</a>
<div class="corner-ribbon blue-light" *ngIf="isARecentSource(source.createdAt)">New</div>
<div class="corner-fav" [class.is-active]="isSourceFavorite(source.id)"
(click)="toggleFavorite(source.id)"><em class="icon-heart-o"></em></div>
</div>
<a class="content" routerLink="{{ organization?.name | routes: 'sources' : source.id }}">
<div class="icon">
<em [ngClass]="source.icon? 'icon-'+source.icon : 'icon-cocktail3'"></em>
</div>
</a>
<div class="actions">
<ul class="buttons three">
<li class="icon-font" ><a (click)="openDashboardModal(source)"
[class.disable]="source.powerbi.length === 0" class="btn-effect"><em
class="icon-chart-bar"></em></a></li>
<li class="icon-font"><a
(click)="openDatalakeModal(source)" [class.disable]="source.datalakePath.length === 0"
class="btn-effect"><em class="icon-arrow-to-bottom"></em></a>
</li>
<li class="icon-font"><a routerLink="{{ organization?.name | routes: 'sources' : source.id }}"
class="btn-effect"><em class="icon-info-circle"></em></a></li>
</ul>
</div>
</div>
</div><!-- /item -->
And I want to target part of this card like a button for example as :
<li class="icon-font" id="step4{{idx}}">
<a (click)="openDashboardModal(source)" [class.disable]="source.powerbi.length === 0" class="btn-effect">
<em class="icon-chart-bar"></em>
</a>
</li>
and then in my component :
const intro = IntroJs.introJs();
intro.setOptions({
steps: [
{
element: document.querySelector('#step40'),
intro: 'Welcome to the Data Portal ! Explore and download data accross any division. Let\'s discover the new home page.'
}]})
intro.start();
Is there something I am doing wrong ? is introjs not able to do it at all ? is there another lib that can do it?
Thank you in advance
The sections generated by the *ngFor loop may not be rendered yet in the AfterViewInit event. To detect the presence of these elements, you should use #ViewChildren and monitor the QueryList.changes event.
In the template, define a template reference variable #step instead of id:
<li class="icon-font" #step>
<a (click)="openDashboardModal(source)" [class.disable]="source.powerbi.length === 0" class="btn-effect">
<em class="icon-chart-bar"></em>
</a>
</li>
In the code, retrieve the elements with #ViewChildren, and subscribe to the QueryList.changes event. You may have to convert the QueryList to an array to access an element with a specific index.
#ViewChildren("step") steps: QueryList<ElementRef>;
ngAfterViewInit() {
this.startIntroJs(); // Maybe already present
this.steps.changes.subscribe((list: QueryList<ElementRef>) => {
this.startIntroJs(); // Created later
});
}
private startIntroJs(): void {
if (this.steps.length > 40) {
const intro = IntroJs.introJs();
intro.setOptions({
steps: [
{
element: this.steps.toArray()[40].nativeElement,
intro: 'Welcome to the Data Portal...'
}
]
});
intro.start();
}
}

My single page application which is implemented using AngularJS is taking long time to load the page

I am working on the Single page application and using AngularJS. In my application, all DOM elements get loads using ajax and due to this I have used number of ng-repeat and binding expression and this is the reason my page is taking long time to load the page. Please help to solve my issue.
app
angular.module('tabApp', []);
Service
angular.module('tabApp')
.service('mrcService', ['$http', function($http) {
this.categories = [];
this.getCategories = function() {
return $http({
method: "GET",
url: 'mrcdata.aspx'
}).success(function(data) {
categories = data;
return categories;
});
};
}]);
Controller
angular.module('tabApp')
.controller('dynamicContentCtrl', ['$scope', 'mrcService', function($scope, mrcService) {
$scope.categories = [];
mrcService.getCategories().then(function(response) {
$scope.categories = response.data.Categories;
});
}]);
HTML code
<div class="main-content container" ng-controller="dynamicContentCtrl">
<div ng-controller="desktopTabCtrl" class="row desktop-content">
<div class="col-md-3 col-sm-4 category-items">
<nav class="nav categories-nav">
<ul class="categories">
<li ng-repeat="category in categories" class="category" ng-class="{ active: isSet(category.CategoryRank) }">
<a href="javascript:void(0)" ng-click="setTab(category.CategoryRank)" class="text">
<span>{{category.CategoryTitle}}</span>
</a>
<span class="arrow-right"></span>
</li>
<li class="category static">
<C5:LocalLiteral runat="server" Text="mrc.home.learnmoretab"/>
</li>
</ul>
</nav>
</div>
<div class="col-md-9 col-sm-8 document-tiles">
<div ng-repeat="category in categories" ng-show="isSet(category.CategoryRank)" class="tile-container">
<div class="row">
<div ng-repeat="document in category.Documents" class="col-md-6 col-sm-6 tile">
<div class="tile-content row">
<div class="col-md-12 col-sm-12">
<div class="thumbNail-content col-md-6 col-sm-6">
<p class="title">{{document.DocumentTitle}}</p>
<p class="audience">{{document.Audience}}</p>
</div>
<div class="thumbNail-image col-md-6 col-sm-6">
<img alt="Thumb Nail" src="{{document.ThumbnailUrl}}">
</div>
</div>
<div class="col-md-12 col-sm-12">
<div class="download-section">
<select class="lang-dropdwn"
ng-model="document.DefaultDialectId"
ng-change="selectLang(document , document.LocalizedDocuments , document.DefaultDialectId )">
<option ng-repeat="localizedDocument in document.LocalizedDocuments"
value="{{localizedDocument.DialectId}}">
{{localizedDocument.LanguageName}}
</option>
</select>
</div>
<div class="button-conatiner" ng-init="document.DownloadLink = document.DocumentId +':'+document.DefaultLocalizedDocumentId">
<a class="button" href="documentdownloader.aspx?documentid={{document.DownloadLink}}"><C5:LocalLiteral runat="server" Text="basket.esddelivery"/></a>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
Here's a few options you can try to improve performance.
1. You should set the track by in the ng-repeats
To minimize creation of DOM elements, ngRepeat uses a function to "keep track" of all items in the collection and their corresponding DOM elements. For example, if an item is added to the collection, ngRepeat will know that all other items already have DOM elements, and will not re-render them.
The default tracking function (which tracks items by their identity) does not allow duplicate items in arrays. This is because when there are duplicates, it is not possible to maintain a one-to-one mapping between collection items and DOM elements.
If you do need to repeat duplicate items, you can substitute the default tracking behavior with your own using the track by expression.
For example, you may track items by the index of each item in the collection, using the special scope property $index.
Example:
<div ng-repeat="item in items track by $index">{{item.name}}</div>
2. You could use one way data binding to items you know wont be changed. This will disable the watchers for those items and improve performance. Example:
Instead of this
{{item.name}}
Use this
{{::item.name}}
Please remember that this is a one way binding and these values will not be updated if something changes in your scope but you will need to manually update it.
3. Try to limit the ng-show and ng-hide attributes since this will add more watchers and leads to poor performance.

Ember navbar UI condition based on currentPath

I must not be doing something right. I have the following:
application.hbs
{{#view App.NavbarView}}{{/view}}
{{outlet}}
with the following template for Navbar
_navbar.hbs
<div class="toolbar">
<div class="row">
<div class="absolute top-left">
<button {{action "back"}} class="btn passive back"><i class="fa fa-play"></i></button>
</div>
{{#if hasTabs}}
<div class="small-centered columns">
<div class="tabs">
<ul>
{{#link-to 'stories' tagName="li" class="tab"}}<i class="fa fa-book"></i> Stories{{/link-to}}
{{#link-to 'mylevels' tagName="li" class="tab"}}<i class="fa fa-user"></i> My Levels{{/link-to}}
{{#link-to 'arcade.index' tagName="li" class="tab"}}<i class="fa fa-gamepad"></i> Arcade{{/link-to}}
</ul>
</div>
</div>
{{else}}
<div class="small-6 small-offset-3 columns">
<h2 class="title">{{ pageTitle App.currentPath }}</h2>
</div>
{{/if}}
{{#if currentUser.userName}}
<div class="absolute top-right">
<span class="user-hello">Welcome Back, <strong>{{ currentUser.userName }}</strong></span>
<button {{action "transitionAccount" currentUser._id}} class="square logged-in"><i class="fa fa-user"></i></button>
</div>
{{ else }}
<div class="absolute top-right">
<button {{action "transitionLogin"}} class="square logged-out"><i class="fa fa-user"></i></button>
</div>
{{/if}}
</div>
</div>
So all it is is a typical fixed navbar and in the middle of it I display what page you are on, if you happen to be on a page that has tabbed content, I show a tab container instead.
So I'm just using this.get('currentPath') in my App controller and comparing it against a group of route names to trigger true/false (I need an observer so it looks at the route change since the Navbar is in inline view at the Application level).
app.js
App.ApplicationController = Ember.ObjectController.extend({
updateCurrentPath: function() {
App.set('currentPath', this.get('currentPath'));
}.observes('currentPath'),
tabs: function() {
var route = this.get('currentPath'),
group = ['arcade.index', 'mylevels', 'stories', 'arcade', 'arcade.loading'];
console.log("ROUTE: ", route);
var tabs = group.indexOf(route) > -1 ? true : false;
return tabs;
}.observes('currentPath'),
// no idea what to do here
hasTabs: function() {
this.tabs();
}.property('tabs')
});
So, basically, no matter what, the tab UI is showing up, but I only want it to show up if that tabs observer is true. With some debugging I'm getting all the console output I would expect but I tried just doing {{#if tabs}} (just using the observer directly) and that always fires true (always shows the tabs UI). I assumed that's because it was an observer and not an actual controller property I could use in my template, so I tried just setting the hasTabs property and referencing the observer, but that doesn't seem to work. I realize I am fundamentally not understanding how this should work. Any thoughts?
If I understand your question correctly you should be able to just change your code to this (renamed tabs to hasTabs, removed previous hasTabs function. Changed from observes currentPath to be property of current path, removed the tabs variable assignment and replaced with the return, reduced the boolean conditional to the simple comparison operator). This is what I'd do, anyway. :) H2H
hasTabs: function() {
var route = this.get('currentPath'),
group = ['arcade.index', 'mylevels', 'stories', 'arcade', 'arcade.loading'];
return group.indexOf(route) > -1;
}.property('currentPath')

How to do a foreach binding using KnockoutJS in Bootstrap-themed dropdown

I'm developing a small notifications-like module, where the user can see his 5 latest activities that are logged in the DB (MSSQL). The values that I need are all there, but for some reason knockout binding is not working. Here are code snippets:
<div class="dropdown-menu toolbar pull-right" data-bind="with: layoutLogsModel">
<h3 style="border: none;">Recent activities:</h3>
<!-- "mailbox-slimscroll-js" identifier is used with Slimscroll.js plugin -->
<ul id="mailbox-slimscroll-js" class="mailbox" data-bind="foreach: layoutLogsModel.notification">
<div class="alert inbox">
<a href="javascript:void(0)">
<i class="icon-book" style="color: orange;"></i>
Some text
</a>
<br>
Some text #2
</div>
</ul>
</div>
For now, I only want to display random text for every item that is in the observableArray.
ViewModel is the following:
var layoutLogsModel = {
notification: ko.observableArray()
};
function getLastFiveActivities() {
get(apiUrl + "Logs/GetLastFiveActivities", { ClientUserID: loggedUserID }, function (data) {
layoutLogsModel.notification(data);
});
}
And every time I call this function, the list is empty (IMAGE)
(the function is called on click, and absolutely no errors are shown in the console).
What is it that I am doing wrong?
EDIT:
The thing was, I forgot to execute ko.applyBindings for that viewModel. Then, I changed the HTML to look like this:
<ul id="mailbox-slimscroll-js" class="mailbox" data-bind="foreach: notification">
<div class="alert inbox">
<a href="javascript:void(0)">
<i class="icon-user" style="color: green;"></i>
<span data-bind="text: $data"></span>
</a>
</div>
</ul>
Aslo, I modified the get function slightly, like this:
function getLastFiveActivities() {
get(apiUrl + "Logs/GetLastFiveActivities", { ClientUserID: loggedUserID }, function (data) {
layoutLogsModel.notification(data.Notification);
});
}
(changed data to data.Notification based on the MVC model property that contains the array)
After all that, the data was available immediately.
try removing the layoutLogsModel from the foreach, you are already using it with the binding "with", so eveything in that div will be part of layoutLogsModel.
<div class="dropdown-menu toolbar pull-right" data-bind="with: layoutLogsModel">
<h3 style="border: none;">Recent activities:</h3>
<!-- "mailbox-slimscroll-js" identifier is used with Slimscroll.js plugin -->
<ul id="mailbox-slimscroll-js" class="mailbox" data-bind="foreach: notification">
<div class="alert inbox">
<a href="javascript:void(0)">
<i class="icon-book" style="color: orange;"></i>
Some text
</a>
<br>
Some text #2
</div>
</ul>
</div>

Categories

Resources