Jeg knoter med jquery og feed-bilder

Fritz

Medlem
Hei! Håper noen har tid og lyst til å hjelpe meg med dette. Jeg prøver å lage et nyhetsfeed med flere kilder, og som også skal vise bilder. (link til tutorial med scriptet) Jeg har fått den til å vise bilder fra alle kilder som legger de ved, likevel er det noen ganger at bildet ikke kommer eller kommer med feil, og da vises bare et bildeikon. Her er koden jeg bruker til bildene:

item.image = $(this).find("enclosure").attr('url');
if ($.isEmptyObject(item.image)) {
item.image = $(this).find("media\\:content").attr('url');
}
else if ($.isEmptyObject(item.image)) {
item.image = item.websiteLink + ".png";
}

Den siste if seksjonen virker ikke. Det er vel kanskje fordi den har samme klausul som den over. Den er ment for de feedene som ikke kommer med bilde, og den kunne også gjerne gjort susen der bildene kommer med feil. Den virker på noen "ikke-bilder" hvis jeg bare har et alternativ over.

Jeg har også prøvd med en onerror="myfunction()" i img koden, men det (eller måten jeg gjorde det på) bare ødela skriptet. Koden der bildet vises ser slik ut:

imageElement.innerHTML = '<img src="' + item.image + '" />';

og jeg prøvde dette:

imageElement.innerHTML = '<img src="' + item.image + '" onerror="myfunction()" />';

Og dette:

imageElement.innerHTML = '<img src="' + item.image + '" onerror="' + myfunction() + '" />';

Bildeikonet er høyreklikkbart, men kommer som "example.com/undefined" hvis jeg trykker "vis bilde".

Noen som har ideer om måter dette kan gjøres? :)
 

adeneo

Medlem
Du må nok ut med noe mer informasjon, det er litt vanskelig å følge?

For det første
Kode:
item.image = $(this).find("enclosure").attr('url');
ser etter et element som ser ut som
Kode:
<enclosure url="bilde.png"></enclosure>
er det slik XML'en din ser ut ?

For det andre, $.isEmptyObject sjekker om et objekt ikke har noen "enumerable properties" som det heter så fint, altså at det er "{}".
jQuery's "attr()" vil alltid returnere "string", altså ikke et objekt i det hele tatt ?

Videre så er det altså

Kode:
if ($.isEmptyObject(item.image)) {
    ....
} else if ($.isEmptyObject(item.image)) {
    ....
}
gir liten mening, du sjekker det samme i begge ?

For å slenge noe ut, med litt asynkrone ting og promises og slikt

PHP:
var self = this;

testImage( $(self).find("enclosure").attr('url') ).then(null, function(err) {
    return testImage( $(self).find("media\\:content").attr('url') );
}).then(null, function(err) {
    return testImage( item.websiteLink + ".png" );
}).then(null, function(err) {
    return testImage( 'http:/fiffigefritz.no/error.png' ); // standardbilde for feil etc.
}).then(function(image) {
    $(imageElement).append(image);
});

function testImage(url) {
    var def       = new $.Deferred();
    var image     = new Image();
    image.onload  = function() { def.resolve(image) }
    image.onerror = def.reject;

    url && url.length ? image.src = url : def.reject();
   
    return def.promise();
}
 

Fritz

Medlem
Takk for svar! Det er litt sånn det blir når man er en vranglært klippoglim-koder, sånn som meg. :)
Noe av problemet med å hente feed fra flere kilder er jo at xml'ene ikke er like, og at man derfor trenger å sjekke på flere måter for å finne bildene hos alle. Noen av kildene sender aldri bilder, og da er det greit med den "websitelink.png"-varianten slik at bildet hvertfall viser hvor feed-itemet kommer fra.

Å bruke "($.isEmptyObject(item.image))" var noe jeg leste at var måten man skulle gjøre det, men her har jeg tydeligvis misforstått det jeg leste. Opprinnelig brukte jeg" if (item.image === ", og det virket det også.

Jeg forstår visst ikke helt hvordan jeg skal bruke koden du la ved. Jeg bare limte det inn mellom andre funksjoner, og linket til det med "item.image = testImage();". Det var tydeligvis ikke måten å gjøre det på, for da ble alle bildene "example.com/[object Object]".

Den nederste biten av det forstår jeg ikke hva gjør i det hele tatt. Det øverste ser ut som noe som kunne funket hvis bare utfallet hadde vært "item.image = (....)" istedet for "return(....)". :) Jeg forstår heller ikke hvorfor du bruker "self" istedet for "this".
 

adeneo

Medlem
Den nederste funksjonen er asynkron, og den returnerer altså noe på et senere tidspunkt, derav bruken av "then()", det er en såkalt "thenable".
Det beste ville være å bruke A+ promises, men støtten i nettlesere er ikke helt god enda, men jQuery har en heller dårlig $.Deferred() løsning som også funker til slike ting (som faktisk følger A+ standarden i jQuery 3.0 Apha, så det kommer etterhvert).

Funksjonen tar altså i mot en lenke til et bilde, og sjekker om lenken eksisterer, altså både om den er tom, og om bildet faktisk kan lastes inn.
Det å sjekke om bildet kan lastes inn, eller med andre ord funksjonene "onload()" og "onerror()", er asynkrone.
Dersom lenken eksisterer, og bildet kan lastes inn, så returneres et <img> element, som i javascript er et objekt, og dette må settes inn med appendChild eller lignende metoder (ikke med innerHTML).

Nå tok jeg bare de snuttene du la ut, og gjettet villt, men man trenger altså en eller annen form for callback her dersom bildelenkene skal sjekkes.

Det så ut som om du forsøkte å teste flere lenker, og jeg skrev det derfor slik at flere lenker ble sjekket i en kjede, eller "chainable promises"

PHP:
testImage( $(self).find("enclosure").attr('url') ).then(null, function(err) {
    return testImage( $(self).find("media\\:content").attr('url') );
}).then(null, function(err) {
    return testImage( item.websiteLink + ".png" );
}).then(null, function(err) {
    return testImage( 'http:/fiffigefritz.no/error.png' ); // standardbilde for feil etc.
}).then(function(image) {
    $(imageElement).append(image);
});

Dette er veldig krøkkete skrevet med jQuery, og med ES2015 A+ Promises ville det kanskje være enklere å forstå

PHP:
var self = this;
var url  = $(self).find("enclosure").attr('url');

testImage(url).catch(function(error) {
    // hvis bilde #1 ikke kan lastes, gå hit
    var url2 = $(self).find("media\\:content").attr('url');
    return testImage(url2); // sjekk bilde 2, og returner et "løfte"
}).catch(function(error) {
    // hvis bilde #2 ikke kan lastes, gå hit
    var url3 = item.websiteLink + ".png";
    return testImage(url3); // sjekk bilde 3, og returner et "løfte"
}).then(function(bilde) {
    // "bilde" vil være det første av de ovenfor som kan lastes inn med suksess
    $(imageElement).append(image);
});

function testImage(url) {
    return new Promise(resolve, reject) {
        if ( url && url.length ) {   // hvis url eksisterer, og ikke er tom
            var image = new Image(); // lag nytt bildeelement
          
            image.onload  = function() {  // bildet kunne lastes inn
                resolve(image); // returnerer bildet som suksess
            }

            image.onerror = function() { // bildet kunne ikke lastes inn
                reject('Feil : bildet kunne ikke lastes inn'); // returnerer feilmelding
            }
          
            mage.src = url; // sett bildets "src"
        } else { // hvis url ikke eksisterer, eller er tom
            reject('Feil : lenken er tom !'); // returnerer feilmelding
        }
    });
}

Årsaken til at det brukes en variabel for "this", er fordi verdien av "this" er "function-scoped", altså endres verdien av "this" hver gang man lager en ny funksjon, og "this" inne i funksjonene er ikke det samme som "this" utenfor funksjonene (nå kommer også block-scoping og lexical "this" i ES2015, for eksempel i arrow-funksjoner, men det er helt irrelevant her, og jeg bare tar det med som tilleggsinfo for de spesielt interesserte).
 

Fritz

Medlem
Jeg klippet og limte litt fra forslaget ditt også, Adeneo. Kom frem til dette, som for meg ser ut som det skulle kunne funke:

item.image = $(this).find("enclosure").attr('url');
if (item.image === null).then(function(err) {
item.image =( $(this).find("media\\:content").attr('url') );
}).then(null, function(err) {
item.image =( item.websiteLink + ".png" );
}).then(null, function(err) {
item.image =( 'http:/fiffigefritz.no/error.png' ); // standardbilde for feil etc.
});

Virket ikke, så jeg fjernet noen paranteser som jeg synes så overflødige ut :) slik:

item.image = $(this).find("enclosure").attr('url');
if (item.image === null).then(function(err) {
item.image = $(this).find("media\\:content").attr('url');
}).then(null, function(err) {
item.image = item.websiteLink + ".png";
}).then(null, function(err) {
item.image = 'http:/fiffigefritz.no/error.png'; // standardbilde for feil etc.
});

Men det virket desverre heller ikke, så jeg har vel gjort noe feil her og. :)

Jeg setter stor pris på hjelpen fra deg, Adeneo. Jeg sliter litt med å få koding til å bli logisk for meg, så jeg pleier å skylde på at jeg er mer mekaniker-anlagt. Men så har jeg fått med meg at du er en rimelig habil mekaniker også, så jeg må visst finne noe annet å skylde på. :) Flink til å formidle på en lærerik måte er du også, så det er alltid moro å lese det du skriver. Takk igjen!
 

Fritz

Medlem
Der satt jeg og skrev mens du svarte. Som du ser så har jeg ikke helt skjønt den "then"-greia. :)

edit: å gjøre det til et img-element, samt å "appende" det til html gjøres i andre deler av scriptet, og fungerer som det skal. Det er bare if-klausulene for å ende opp med en url uansett jeg prøver å få til.
 
Sist redigert:

Fritz

Medlem
Da har jeg fått det til slik jeg ønsket. Med denne snutten:

if ($(this).find("enclosure").attr('url'))
{
item.image = $(this).find("enclosure").attr('url');
}
else if ($(this).find("media\\:content").attr('url'))
{
item.image = $(this).find("media\\:content").attr('url');
}
else item.image = item.websiteLink.replace(/\//g, "") + ".png";

Vet ikke hvor riktig dette er, men det virker som det skal. :)

Nå trenger jeg bare å få delt det opp i kolonner. Noen som kan gi et hint om hvordan det kan gjøres? Scriptet finnes i kildekoden på demoen her.
 
Sist redigert:

Fritz

Medlem
Mitt beste forsøk til nå. På linje 179 i demoscriptet fant jeg denne:

if(settings.limitItems > 0 && itemsDisplayed >= settings.limitItems) break;

Utfra den var dette det beste jeg kom på:

if(settings.colsLength > 0 && itemsDisplayed >= settings.colsLength)
{
$('ul#news').html.append(' </ul><ul> ');
}

Stylet ul'en med width 33% og display inline, og lagde "colsLength = 33" i defaults.

Dette endrer antallet feeds, men det blir altså ikke flere kolonner.

Jeg har slicet arrayer med feeds til kolonner i php før, men i dette scriptet klarer jeg ikke å finne ut hvor og hvordan det evt skal gjøres. Jeg har prøvd minst 5 forskjellige plugins og andre snutter som sier de skal slice ul'er opp i kolonner, men ingen av de har fungert (eller jeg har gjort noe feil med de).

Noen som kan hjelpe med et hint eller tre? :)
 

adeneo

Medlem
Dersom du skal ha en liste for hver kilde, kan du vel bare bruke tre elementer, sånn

Kode:
$('#news_1').newswidget({
        source: 'lenke_1',
        ...options
});

$('#news_2').newswidget({
        source: 'lenke_2',
        ...options
});

$('#news_3').newswidget({
        source: 'lenke_3',
        ...options
});


Dersom det du prøver på er å lage tre kolonner av det felles resultatet du får når du bruker flere lenker i samme "newswidget", så er det nok ikke fullt så enkelt, du må ha tre elementer, og så dytte en tredjedel av listen i hvert element osv. som betyr at plugin'en må skrives om.

For å ta koden du forsøker først
Kode:
$('ul#news').html.append(' </ul><ul> ');

Et "protip" er at jQuery selector'er leses fra høyre til venstre, slik $('ul#news') er noe man generelt ikke bør gjøre.
Jeg ser at det er gjort på den måten i scriptet du forsøker å bruke, og det virker, men er lite effektivt.
Dersom man benytter bare ID'en, som uansett er unik, så benytter jQuery getElementById internt, altså bør det skrives kun $('#news').

Videre er det enten html() eller append(), du kan ikke legge de sammen, og du kan ikke bruke deler av et element, du må bruke hele elementer, slik at disse to er gyldige

Kode:
$('#news').html('<ul></ul>'); // sletter alt og legger inn en UL
$('#news').append('<ul></ul>'); // legger til en UL på slutten


Nå er det en hel del slik feil i det scriptet, for eksempel for...in loops over arrays, som bør unngås osv.

Det er jo ikke så lett å skrive om en hel plugin på et par minutter, særlig ikke med så mye feil og rar kode, men jeg forsøker

https://jsfiddle.net/x8rnb4bh/
 

Fritz

Medlem
Jeg vil helst ikke ha en kolonne for hver kilde. Da er jeg låst til å måtte ha like mange kolonner som kilder. Jeg bruker 5 kilder nå til testing.

Jeg prøvde nå $('#news').html('<ul></ul>'); .

Da viste den bare et feed som var fra 6 timer siden.

Så prøvde jeg $('ul#news').html.append('<ul></ul>');.

Da viser den 33 feeds hvor det siste er fra 1 time siden.

Så prøvde jeg $('ul#news').append('<ul></ul>');.

Da viste den 99 feeds hvor det siste er fra 6 timer siden.

Ikke lett å bli klok på. Men kanskje ikke så rart hvis scriptet er fullt av feil fra før. Takk for at du prøver! :)
 

Fritz

Medlem
Her er hvordan jeg gjorde det i php med simplepie:
$blocks = array_slice($items, 0, 20);
foreach ($blocks as $block)
Feeds her
$blocks = array_slice($items, 20, 20)
foreach ($blocks as $block)
Feeds her
$blocks = array_slice($items, 40, 20)
foreach ($blocks as $block)
Feeds her
 

adeneo

Medlem
Nå la jeg jo til en lenke nederst i mitt forrige innlegg med en del omskrivninger av den plugin'en.

Jeg har ikke tid til å bruke timevis, men en kjapp test med YQL som proxy ser ut til å virke

Her -> https://jsfiddle.net/36j6v2t0/1/

(merk at den demoen er omskrevet for å benytte YQL, scriptet er ellers likt som i mitt forrige innlegg).
 

Fritz

Medlem
Beklager, jeg så ikke den fele-linken! Tusen takk nok en gang! Jeg fikk den ikke til å virke sånn bare ved å bytte fila, men nå ser jeg hvordan det kan gjøres så da skal jeg nok klare å få det til. PS. Jeg får ikke den siste fela til å virke heller. Står bare "Venligst vent ... laster inn !". :)
 
Topp