TLDR: Binding two radio buttons to the same underlying property results in invalid behavior and the error "ExpressionChangedAfterItHasBeenCheckedError"
Environment: Angular 7.3.8, PrimeNg 7.1.3
Example in StackBlitz:
https://stackblitz.com/edit/angular-primeng-playground-t7nmfx
Ultimately, I understand what's going on here. The two instances of the radio button are interfering with each other trying to set the initial state and also interfere when setting one or the other. But I can't see how to get out of this. Obviously, this is a super simplified example, and the screen layout was provided to me so I can't move the radio outside of the tab. I really somehow need two of them. Also I'm not sure if this is related to PrimeNG or not.
Html:
<p-tabView>
<p-tabPanel header="today">
<div>
Temperature: {{tempToday}}
</div>
<div>
Show in:
<p-radioButton name="groupname" [value]="1" [(ngModel)]="isCelcius"></p-radioButton> C
<p-radioButton name="groupname" [value]="0" [(ngModel)]="isCelcius"></p-radioButton> F
</div>
</p-tabPanel>
<p-tabPanel header="tomorrow">
<div>
Temperature: {{tempTomorrow}}
</div>
<div>
Show in:
<p-radioButton name="groupname" [value]="1" [(ngModel)]="isCelcius"></p-radioButton> C
<p-radioButton name="groupname" [value]="0" [(ngModel)]="isCelcius"></p-radioButton> F
</div>
</p-tabPanel>
</p-tabView>
Component:
ngOnInit() {
this.isCelcius = false;
this.tempToday = 78;
this.tempTodayInF = 78;
this.tempTodayInC = 20;
this.tempTomorrow = 80;
this.tempTomorrowInF = 80;
this.tempTomorrowInC = 21;
}
public get isCelcius(): boolean {
return this._isCelcius;
}
public set isCelcius(value: boolean ) {
this._isCelcius = value;
if(this._isCelcius) {
this.tempTomorrow = this.tempTomorrowInC;
this.tempToday = this.tempTomorrowInC;
} else {
this.tempTomorrow = this.tempTomorrowInF;
this.tempToday = this.tempTodayInF;
}
}
private _isCelcius: boolean;
public tempToday: number;
public tempTomorrow: number;
private tempTodayInF: number;
private tempTodayInC: number;
private tempTomorrowInF: number;
private tempTomorrowInC: number;
I'm going to leave this here in case anyone ever runs into the same problem.
The issue isn't what it appears to be. What's causing the problem is that the radio button group name is duplicated. Changing the group name in either of the tabs fixes the issue.
<p-radioButton name="groupname1" [value]="1" [(ngModel)]="isCelcius"></p-radioButton> C
<p-radioButton name="groupname1" [value]="0" [(ngModel)]="isCelcius"></p-radioButton> F
Related
Im doing MVC4 application and Im creating Selenium tests for it. My problem is that I want to find element <ul class="connectors ui-sortable"></ul> but only contained in <li class="empty zoneLi ui-droppable" data-order="X">
My solution was one added below, but it's not working:
private static IWebElement FindZoneByDataOrder(IWebDriver webDriver, int dataOrderId)
{
var parentForDropElement = webDriver.FindElement(By.XPath("//li[#data-order='" + dataOrderId + "']"));
var dropElement = parentForDropElement.FindElement(By.XPath("//ul[#class='connectors ui-sortable']"));
return dropElement;
}
part of my HTML page:
....
<div class="zones-system-creator" style="min-height: 430px;">
<ul id="zonesCreateSystem" class="">
<li data-zoneid="24829" class="empty zoneLi ui-droppable" data-order="1">
<div id="warningInfoBoxContainer"></div>
<ul class="products ui-sortable"></ul>
<ul class="connectors ui-sortable"></ul>
</li>
</ul>
</div>
....
UPDATE1: I changed zoneID to dataOrderId, because we care only about dataOrderId.
I see few issues in your code. You talked about data-order, but passed zoneId in the method. So I have added another parameter called dataOrder and searching for the LI element which has both zone and data order attributes:
"//li[#data-zoneid='" + zoneId + "'][#data-order='"+dataOrder+"'"]
Then searching for ul element of having specified class. Also combined finding the element into a single line:
Code:
private static IWebElement FindZoneByDataOrder(IWebDriver webDriver, int zoneId, int dataOrder)
{
return driver.findElement(By.xpath("//li[#data-zoneid='" + zoneId + "'][#data-order='"+dataOrder+"']//ul[#class='connectors ui-sortable']"))");
}
Edited Answer following question edit:
return driver.findElement(By.xpath("//li[#data-order='"+dataOrderId+"']//ul[starts-with(#class, 'connectors')]"));
I found 2 working solutions if you want to find one element inside the other with Selenium:
result = webDriver.FindElement(By.XPath("//li[#data-order='" + dataOrderId+ "']"));
var result2 = result.FindElement(By.XPath(".//ul[#class='connectors']"));
result = webDriver.FindElement(By.CssSelector("li[data-order='" + dataOrderId+ "']"));
result = result.FindElement(By.CssSelector("ul[class='connectors']"));
I have a pretty straight-forward range input slider. It works pretty well on all browsers except on iOS Safari.
The main problem I have is when I click on the slider track, it doesn't move the slider. It merely highlights the slider. It works fine when dragging the thumb. Any ideas on how to fix this?
<div class="slider-wrap">
<div id="subtractBtn"></div>
<input type="range" class="slider">
<div id="addBtn"></div>
</div>
For those still looking for a javascript only fix like I did. I ended up just making a "polyfill" for this; it relies on the max attribute of the slider and determines the best step to force the input range on iOS. You can thank me later :)
var diagramSlider = document.querySelector(".diagram-bar");
function iosPolyfill(e) {
var val = (e.pageX - diagramSlider.getBoundingClientRect().left) /
(diagramSlider.getBoundingClientRect().right - diagramSlider.getBoundingClientRect().left),
max = diagramSlider.getAttribute("max"),
segment = 1 / (max - 1),
segmentArr = [];
max++;
for (var i = 0; i < max; i++) {
segmentArr.push(segment * i);
}
var segCopy = segmentArr.slice(),
ind = segmentArr.sort((a, b) => Math.abs(val - a) - Math.abs(val - b))[0];
diagramSlider.value = segCopy.indexOf(ind) + 1;
}
if (!!navigator.platform.match(/iPhone|iPod|iPad/)) {
diagramSlider.addEventListener("touchend", iosPolyfill, {passive: true});
}
<input type="range" max="6" min="1" value="1" name="diagram selector" class="diagram-bar" />
Add the CSS property, -webkit-tap-highlight-color: transparent to the CSS of the element or the complete html page. This will remove the troublesome highlight effect on an element when it is tapped on a mobile device.
Enhanced version of the solution presented by Cameron Gilbert. This implementation works for sliders with a min value set to a negative number and optional a step size set.
For those not working with typescript I leave it to you to convert to plain javascript.
if (!!navigator.platform.match(/iPhone|iPad|iPod/)) {
mySlider.addEventListener(
"touchend",
(touchEvent: TouchEvent) => {
var element = touchEvent.srcElement as HTMLInputElement;
if (element.min && element.max && touchEvent.changedTouches && touchEvent.changedTouches.length > 0) {
const max = Number(element.max);
const min = Number(element.min);
let step = 1;
if (element.step) {
step = Number(element.step);
}
//Calculate the complete range of the slider.
const range = max - min;
const boundingClientRect = element.getBoundingClientRect();
const touch = touchEvent.changedTouches[0];
//Calculate the slider value
const sliderValue = (touch.pageX - boundingClientRect.left) / (boundingClientRect.right - boundingClientRect.left) * range + min;
//Find the closest valid slider value in respect of the step size
for (let i = min; i < max; i += step) {
if (i >= sliderValue) {
const previousValue = i - step;
const previousDifference = sliderValue - previousValue;
const currentDifference = i - sliderValue;
if (previousDifference > currentDifference) {
//The sliderValue is closer to the previous value than the current value.
element.value = previousValue.toString();
} else {
element.value = i.toString();
}
//Trigger the change event.
element.dispatchEvent(new Event("change"));
break;
}
}
}
},
{
passive: true
}
);
}
Safari 5.1 + should support type "range" fully - so it's weird it's not working.
did you try adding min/max/step values ? what about trying to use only the pure HTML attribute without any classes that might interfere - try pasting this code and run it on a Safari browser
<input type="range" min="0" max="3.14" step="any">
it should be working - if it does, it's probably something related to your css, if not try using one of the pure HTML examples here:
https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/range
Try using jQuery Mobile? The workaround is obnoxious, and I'm pretty sure there's no way to use the native interface with standard HTML. It appears from other threads, you could do an elaborate CSS/JS solution.
I am trying to pull out lines in a tab delimited text file which contain all user-specified words exactly once (the sequence doesn't matter).
For example, I need to find lines which contain 'CA_', 'CS_', 'XV_' and 'JS_' exactly once.
Can I use grep for that?
Here is a possible solution. Is that what you are trying to do?
var rawString = 'CA_1234567 CA_R345335 CS_I8788765 CA_3456783 CS_0986887 CS_scaffolding2 CA_scaffolding3';
var valArr = rawString.split(' ');
//note: in real code, put CA, CA etc in an array and iterate
var CAItems = $.grep(valArr, function(val)
{
if (val.startsWith('CA'))
{
return val;
}
});
var CSItems = $.grep(valArr, function(val)
{
if (val.startsWith('CS'))
{
return val;
}
});
$('#CAValuesTxt').text(CAItems.join(' '))
$('#CSValuesTxt').text(CSItems.join(' '))
String.prototype.startsWith = function (prefix) {
return this.indexOf(prefix) === 0;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<label>Values starting with CA: </label>
<label id='CAValuesTxt'></label>
<br/>
<br/>
<label>Values starting with CS: </label>
<label id='CSValuesTxt'></label>
I hope it helps!
I am working on Ruby on Rails project and I have implemented markdown syntax for some text descriptions in my project using redcarpet gem.
It works like charm allowing to convert markdown text to HTML as simply as
<%= markdown some_text_variable %>
But now I want to implement preview feature rendering just small part of the full text.
The following naive construction
<%= markdown some_text_variable[0..preview_length] %>
will not work because it can easily break down MD syntax resulting in confusing constructions (imagine, for example, spliting original string on the half of image link).
I came up with
<%= markdown some_text_variable[0..preview_length].split(/\r?\n/)[0..-2].join("\r\n")) %>
but it does not deal, for example, with code blocks.
Is there any way to implement such kind of preview for MD text?
Using markdown.js and / or showdown should work. Here's a StackO with the same question and answer. I personally have used showdown in an Ember app before to render a live preview of the text as it's being typed (via 2-way data binding), and it worked flawlessly.
In the fiddle below, I wrote a little Showdown parser that takes in a string of markdown, splits it on a newline (returns an array of tags), and iterates through the array. On each iteration, it removes the tags, checks the length of the resulting string, and then compares it to the max character count for the preview. Once the next iteration surpasses the max character count, it returns the preview. The do loop ensures that you will always get at least one blob of html as a preview.
Fiddle
$(function() {
var converter = new Showdown.converter();
var previewMax = 200;
$('button').click(function() {
var content = $('#markdown').val(),
charCount = 0,
i = 0,
output = '';
if (!content) {
return $('div.preview').html("Please enter some text.");
}
var mark = converter.makeHtml(content);
var mark_arr = mark.split('\n');
while (charCount < previewMax) {
var html = mark_arr[i];
var text = htmlStrip(html);
if ((charCount + text.length) > previewMax) {
var overflow = (charCount + text.length) - previewMax;
var clipAmount = text.length - overflow;
html = jQuery.truncate(mark_arr[i], { length: clipAmount });
}
output += html;
charCount += text.length;
i++;
};
$('div.preview').html(output);
$('div.full').html(mark);
});
function htmlStrip (html) {
var div = document.createElement('div');
div.innerHTML = html;
var text = div.textContent || div.innerText || "";
return text;
}
});
REVISION
I updated the function using jQuery Truncate to cut the final string into an elipses so that all your previews are the same length as the others. Also, I realized that the original function returned a long string of undefined' over and over when no text was entered, so there is a check to eliminate that. Since this loop will always return at least one html item now, I changed the do loop to a while loop for easier reading. Finally, if you want your truncation to always end at a word boundary, pass the words: true option when you call it. Obviously, this will not give you the same level of truncation for every preview, but it will improve legibility. That's it!
I want to share my preview version it was quite simple with showdown.js and prism.js syntax highlighting.
Prism.js is syntaxing easily with JavaScript and CSS. All you need to pick specific languages and download it to assets folder. Or you can specify it to specific pages.
This is going to happen in realtime preview, in a form.
In Rails form:
<div class="col-md-12">
<div class="form-group">
<%= f.label :body %>
<%= f.text_area :body, class: "form-control", rows: 10 %>
</div>
</div>
<div class="col-md-12">
<h1> Preview Markdown </h1>
<div class="form-group markdownOutput"></div>
</div>
And add this script right below a form page.
<script>
function mkdown(){
var converter = new showdown.Converter(),
$post_body = $("#post_body");
// This line will keep adding new rows for textarea.
function postBodyLengthDetector(post_body){
var lines = post_body.val().split("\n");
post_body.prop('rows', lines.length+5);
}
// Textarea rows in default '10', when focusing on this. It will expand.
$post_body.focus(function(){
postBodyLengthDetector($(this));
$('.markdownOutput').html(converter.makeHtml($post_body.val()));
});
// All simple magic goes here, each time when texting anything into textarea
//it will be generated to markdown. You are able to see preview right below of textarea.
$post_body.keyup(function() {
postBodyLengthDetector($(this));
var value = $( this ).val(),
html = converter.makeHtml(value);
$('.markdownOutput').html(html);
});
}
$(mkdown);
$(document).on("turbolinks:load", mkdown);
</script>
Here's the scenario:
I am using an ASP.NET MVC site with Angular JS and Boostrap UI. I have a dynamic ul list populated by data fed through a controller call to AngularJS, filtering on that list through an input search box. The list is also controlled through pagination (UI Bootstrap control) that I've setup to show 10 results per page for the list of 100 or so items. This list is filtered as the user types in the search box, however I would like the pagination to update as well so consider the following example:
The list has 10 pages of items (100 items), the user types some text in the input search box which filters the list down to 20 or so items, so the pagination should be updated from 10 pages to two pages.
I figure there must be a $watch setup somewhere, perhaps on the list items after it has been filtered and then update the pagination page count, however I'm pretty new to AngularJS so can someone please explain to me how this could be done?
Thanks very much. I have posted my code below:
<div data-ng-app="dealsPage">
<input type="text" data-ng-model="cityName" />
<div data-ng-controller="DestinationController">
<ul>
<li data-ng-repeat="deals in destinations | filter: cityName |
startFrom:currentPage*pageSize | limitTo:pageSize">{{deals.Location}}</li>
</ul>
<br />
<pagination rotate="true" num-pages="noOfPages" current-page="currentPage"
max-size="maxSize" class="pagination-small" boundary-links="true"></pagination>
</div>
var destApp = angular.module('dealsPage', ['ui.bootstrap', 'uiSlider']);
destApp.controller('DestinationController', function ($scope, $http) {
$scope.destinations = {};
$scope.currentPage = 1;
$scope.pageSize = 10;
$http.get('/Deals/GetDeals').success(function (data) {
$scope.destinations = data;
$scope.noOfPages = data.length / 10;
$scope.maxSize = 5;
});
});
destApp.filter('startFrom', function () {
return function (input, start) {
start = +start; //parse to int
return input.slice(start);
};
});
Because your pagination is a combination of chained filters, Angular has no idea that when cityName changes, it should reset currentPage to 1. You'll need to handle that yourself with your own $watch.
You'll also want to adjust your startFrom filter to say (currentPage - 1) * pageSize, otherwise, you always start at page 2.
Once you get that going, you'll notice that your pagination is not accurate, because it's still based on destination.length, and not the filtered sub-set of destinations. For that, you're going to need to move your filtering logic from your view to your controller like so:
http://jsfiddle.net/jNYfd/
HTML
<div data-ng-app="dealsPage">
<input type="text" data-ng-model="cityName" />
<div data-ng-controller="DestinationController">
<ul>
<li data-ng-repeat="deals in filteredDestinations | startFrom:(currentPage - 1)*pageSize | limitTo:pageSize">{{deals.Location}}</li>
</ul>
<br />
<pagination rotate="true" num-pages="noOfPages" current-page="currentPage" max-size="maxSize" class="pagination-small" boundary-links="true"></pagination>
</div>
JavaScript
var destApp = angular.module('dealsPage', ['ui.bootstrap']);
destApp.controller('DestinationController', function ($scope, $http, $filter) {
$scope.destinations = [];
$scope.filteredDestinations = [];
for (var i = 0; i < 1000; i += 1) {
$scope.destinations.push({
Location: 'city ' + (i + 1)
});
}
$scope.pageSize = 10;
$scope.maxSize = 5;
$scope.$watch('cityName', function (newCityName) {
$scope.currentPage = 1;
$scope.filteredDestinations = $filter('filter')($scope.destinations, $scope.cityName);
$scope.noOfPages = $scope.filteredDestinations.length / 10;
});
});
destApp.filter('startFrom', function () {
return function (input, start) {
start = +start; //parse to int
return input.slice(start);
};
});
The version shared on jsfiddle is compatible with ui-bootstrap 0.5.0 but from 0.6.0 onwards there have been breaking changes.
Here is a version that uses the following libraries:
angular 1.2.11
angular-ui-bootstrap 0.10.0
bootstrap 3.1.0
Here is a plunker for the same:
Angular UI Bootstrap Pagination
Hello I tried to hook this up with Firebase using Angular Fire and it only works after I type something in the search input. In the $scope.$watch method, I used Angular Fire's orderByPriorityFilter to convert the object to an array.
$scope.$watch('search', function(oldTerm, newTerm) {
$scope.page = 1;
// Use orderByPriorityFilter to convert Firebase Object into Array
$scope.filtered = filterFilter(orderByPriorityFilter($scope.contacts), $scope.search);
$scope.lastSearch.search = oldTerm;
$scope.contactsCount = $scope.filtered.length;
});
Initial load doesn't load any contacts. It's only after I start typing in the input search field.