Javascript funksjon

kongen

kongemedlem
Jeg har denne koden

PHP:
<script type="text/javascript">
function open(abc) {
	if(document.getElementByName("show"+abc).onclick) 
		document.getElementByName("div"+abc).style.display = 'block';
		document.getElementByName("show"+abc).style.display = 'none';
		document.getElementByName("hide"+abc).style.display = 'inherit';
}

function close(abc) {
	if(document.getElementByName("hide"+abc).onclick) 
		document.getElementByName("div"+abc).style.display = 'none';
		document.getElementByName("hide"+abc).style.display = 'none';
		document.getElementByName("show"+abc).style.display = 'inherit';
}
</script>

Bolle 1<a href="#" onclick="open(1);" name="show1">Vis</a> <a href="#" onclick="close(1);" name="hide1">Skjul</a>
<div name"div1">bolle 1 er god</div>

Bolle 2<a href="#" onclick="open(2);" name="show2">Vis</a> <a href="#" onclick="close(2);" name="hide2">Skjul</a>
<div name"div2">bolle 2 er bedre</div>

Bolle 3<a href="#" onclick="open(3);" name="show3">Vis</a> <a href="#" onclick="close(3);" name="hide3">Skjul</a>
<div name"div3">bolle 3 er best</div>

Jeg vil at det skal vises slik når siden lastes:

Bolle 1 Vis
Bolle 2 Vis
Bolle 3 Vis

Når det klikkes på 'Vis' ved bolle 1 så skal 'Vis' skiftes ut til 'Skjul' og bolle1 divven åpnes

Bolle 1 Skjul
bolle 1 er god
Bolle 2 Vis
Bolle 3 Vis

Hva må forandres i javascript funksjonen får å få til dette?
 

Ole Avranden

utvandret
Jeg blir bare mer og mer nysgjerrig på hva som kommer ut av alle disse spørsmålene dine? Er det til ett konkret prosjekt, flere prosjekter, eller er du bare helt konge og vil lære deg alt som er å kunne?
 

adeneo

Medlem
For det første, med mindre det er form elementer, ikke bruk navn, det er mer krøkkete å jobbe med, mest fordi det ikke finnes noe som heter getElementByName, det heter getElementsByName, og i querySelector må man bruke attribute selectorer, altså "document.querySelectorAll('[name="bolle"]')", det er enklere med klasser og ID'er.

"s"'en i "getElementsByName" betyr alltid at flere elementer kan hentes, ettersom man kan ha samme navn på flere elementer, for eksempel på radio buttons er det vanlig med samme navn på grupper av radio buttons, og derfor returnerer alltid getElementsByName en "nodeList" ikke ett enkelt element.

Dersom du bruker ID i stedet, så blir ting mye enklere, og inline javascript er styggedom, med dagens nettlesere så bruker vi addEventListener.

Det kan være greit å også bruke klasser når man har flere like elementer slik som dette, men klasser returneres selvfølgelig også som en nodeList, altså et array-lignende objekt som inneholder alle elementene, uansett om det bare er ett element som matcher selectoren.

Du trenger heller ikke to anchor elementer, vi kan være litt fancy å lage en "toggle" funksjon som bytter teksten i det ene anchor elementer, så slipper vi å skjule og vise elementer osv.

Hvis vi endrer litt på HTML'en til noe mer fornuftig
PHP:
Bolle 1 : <a href="#" id="show1" class="lenke">Vis</a>
<br />
<div id="div1" class="bolle">bolle 1 er god</div>

Bolle 2 : <a href="#" id="show2" class="lenke">Vis</a>
<br />
<div id="div2" class="bolle">bolle 2 er bedre</div>

Bolle 3 : <a href="#" id="show3" class="lenke">Vis</a>
<br />
<div id="div3" class="bolle">bolle 3 er best</div>

Her har jeg benyttet både ID og klasser, men jeg kunne like gjerne brukt data attributer eller bare plasseringen av elementene, altså finne neste div etter hver anchor fordi strukturen alltid er lik.

Og så bruker vi querySelector, ettersom det er støttet i IE8, mens getElementsByClassName ikke virker i IE8, og querySelector er uansett mye mer anvendelig, samt addEventListener for events

PHP:
// henter alle lenker
var lenker = document.querySelectorAll('.lenke');

// looper over alle lenker og setter en event handler
for (var i=0; i<lenker.length; i++) {
    lenker[i].addEventListener('click', myFunc, false);
}

// funksjonen som blir kjørt når noen klikker
function myFunc(event) {
    event.preventDefault(); // hindrer scrolling ettersom href er #
 
    var tall   = this.id.slice(-1);     // siste tegn i ID'en, vil ikke virke over 9
    var text   = this.textContent.trim(); // IE9 og opp, "innerText" for eldre IE
    var bolle  = document.getElementById('div' + tall);
    var synlig = bolle.style.display;
 
    if (text === 'Vis') { // toggle text
        this.textContent = 'Skjul';
    } else {
        this.textContent = 'Vis';
    }
 
    if (synlig === 'block') { // toggle visibility
        bolle.style.display = 'none';
    } else {
        bolle.style.display = 'block';
    }
 
}

Demonstrasjon

Legg merke til at slike script som henter elementer på siden din, alltid må inkluderes etter elementene, eller må settes inne i en DOMContentLoaded / window.onload funksjon, ettersom elementene må være lastet inn når scriptet kjøres.

Nå er dette skrevet veldig "verbose" som det heter på fagspråket, altså det er skrevet veldig tydelig og enkelt for å være enkelt å lære, det finnes fiffigere måter å gjøre ting på
PHP:
[].slice.call(document.querySelectorAll('.lenke')).forEach(function(x) {
    x.addEventListener('click', function(e) {
        e.preventDefault();
        (function(el, txt) {
            txt = txt.trim() === 'Vis' ? 'Skjul' : 'Vis';
            el.style.display = el.style.display === 'block' ? 'none' : 'block';
        })(document.querySelector('#div' + this.id.slice(-1)), this.textContent);
    }, false);
});

Demonstrasjon
 

adeneo

Medlem
For eksempel helt uten selectorer annet enn tagnames, og med enda mer Array.prototype ( []....call greiene )
PHP:
Bolle 1 : <a href="#">Vis</a>
<br />
<div>bolle 1 er god</div>

Bolle 2 : <a href="#">Vis</a>
<br />
<div>bolle 2 er bedre</div>

Bolle 3 : <a href="#">Vis</a>
<br />
<div>bolle 3 er best</div>
og

PHP:
[].forEach.call([].slice.call(document.querySelectorAll('a')), function(x){
    x.addEventListener('click', function(e) {
        e.preventDefault();
        var el = this;
        while(el && el.tagName !== 'DIV') el = el.nextSibling;
       
        el.style.display = el.style.display === 'block' ? 'none' : 'block';
        this.textContent = this.textContent.trim() === 'Vis' ? 'Skjul' : 'Vis';
    }, false);
});

Demo
 

kongen

kongemedlem
Oj, her var det mye nytt. Takk for svar :)

Jeg fikk alt til å fungere i FireFox, men sliter med å få det til i IE8, der blir #-tegnet stående i adresselinjen som om det var en vanlig link som ble klikket. Jeg la inn denne koden i den første versjonen for å bruke innerText for eldre IE. Ser du hva jeg har gjort feil her?

PHP:
      if ((this.textContent) && (typeof (this.textContent) != "undefined")) {
            var text   = this.textContent.trim();   // IE9 og opp
      } else {
            var text   = this.innerText;   // IE8
      }

Versjon 2 og 3, med sånn []...call greier, er dette noe som også IE8 kan takle hvis man bruker innerText?
 

adeneo

Medlem
Alt er i grunn vanskelig i IE8, og skal du støtte gamle utdaterte nettlesere som selv Google har kuttet støttet for, kan du nesten like gjerne laste ned jQuery med en gang, pass på å laste ned en versjon som støtter IE8 (jQuery har også kuttet støtte for IE8, men har versjoner tilgjengelige foreløpig som støtter gammel elendighet).

For det første er ikke textContent støttet, og det ser ut som du har gjort det riktig, typeof sjekken er fordi IE kan returnere en tom string enkelte ganger, og er nødvendig, og den har du med.
Du må selvfølgelig også bruke innerText for å sette innhold

PHP:
this.innerText = 'Skjul';

For det andre er ikke event.preventDefault støttet, man må gjøre noe sånt i stedet
PHP:
if (event.preventDefault) {
    event.preventDefault();
} else {
    event.returnValue = false; // IE8
}

for det tredje er ikke addEventListener støttet heller
PHP:
if ( lenker[i].addEventListener ){
    lenker[i].addEventListener('click', myFunc, false);
}else{
    lenker[i].attachEvent("onclick", myFunc); // IE8
}

Array.prototype.forEach er heller ikke støttet i IE8, og de siste litt mer avanserte eksemplene bruker dette, men der må det i stedet for brukes en vanlig for loop i IE8.
Legg merke til at jeg la til en lenke to linjer opp, alle javascript funksjoner kan slås opp hos Mozilla, som administrerer ECMA standarden, og helt nederst kan du se hvilke nettlesere som er støttet.
 

kongen

kongemedlem
Jeg sliter litt med å få til dette i IE8. Jeg har nå strippet koden for Firefox ting og bruker bare IE8 ting for å få mer oversikt i koden. Jeg har søkt opp alle javascript tingene i koden og alle skal være IE8 godkjente. Men likevel så vil ikke IE8 oppføre seg. Koden ser nå slik ut:

PHP:
// henter alle lenker
var lenker = document.querySelectorAll('.lenke');

// looper over alle lenker og setter en event handler
for (var i=0; i<lenker.length; i++) {
    lenker[i].attachEvent("onclick", myFunc); // IE8
}

// funksjonen som blir kjørt når noen klikker
function myFunc(event) {
    event.returnValue = false; // IE8
 
    var tall   = this.id.slice(-1);     // siste tegn i ID'en, vil ikke virke over 9
    var text   = this.innerText; // IE8
    var bolle  = document.getElementById('div' + tall);
    var synlig = bolle.style.display;
 
    if (text === 'Vis') { // toggle text
        this.innerText = 'Skjul'; // IE8
    } else {
        this.innerText = 'Vis'; // IE8
    }
 
    if (synlig === 'block') { // toggle visibility
        bolle.style.display = 'none';
    } else {
        bolle.style.display = 'block';
    }
 
}

Noen tips?

Det med JQuery, vil ikke siden lastes mye tregere hvis man skal laste JQuery biblioteket samtidig? Jeg tok en enkelt test med innerText vs JQuery text her:

http://jsperf.com/read-innerhtml-vs-innertext-vs-nodevalue-vs-textcontent/21

Foregår alt nytt med JQuery/AngularJS? Er ren javascript "ute"? Er det lettere å kode i JQuery? Flere funksjoner?

Hvis JQuery biblioteket lastes inn med sideA.com må det lastes på nytt på sideB.com eller ligger det cachet i browseren etter første lasting og tilgjengelig for alle andre sider som trenger det?
 

adeneo

Medlem
Ingen støtter IE8 lengre fordi det er et mareritt, problemet her er at "this" ikke settes når man bruker attachEvent, og at event.returnValue må komme etter koden som skal kjøres.

Da må vi vel over på noe av det vanskeligste å forstå i JavaScript, closures.
Vi gjør om loopen til dette :

PHP:
for (var i=0; i<lenker.length; i++) {
    (function(j) {
        lenker[j].attachEvent("onclick", function() {
            myFunc.call(lenker[j]);
        });
    })(i);
}

Den funksjonen som er satt inn der kalles en IIFE (Immediately Invoked Function Expression).
Det er en funksjon som kjører umiddelbart, og grunnen til at vi bruker den er for å lukke inne verdien av "i", altså en enkel utgave av en closure, litt mer teknisk riktig så lager vi en ny "scope", ettersom funksjoner setter "scope".

Vi kan tenke oss at vi gjorde slik i stedet :
PHP:
for (var i=0; i<lenker.length; i++) {
        lenker[i].attachEvent("onclick", function() {
            myFunc.call(lenker[i]);
        });
}
det ser ut til å være greit, og man skulle tro det ville virke, men det vil det altså ikke.
Årsaken er at loopen kjøres ferdig lenge før vi klikker på noe, og den ytterste "i"en vil bli riktig, men den innerste "i"en, inne i funksjonen som kjøres når vi klikker på noe, vil alltid være den siste verdien "i" ble satt til, fordi loopen ikke venter på at vi skal vi klikke på noe, og derfor ville vi alltid fått det siste elementet inne i .call(lenker), det vil si akkurat her ville "i" alltid vært antall elementer + 1, som ville bety at vi ikke får noe element i det hele tatt, men kun "undefined".

Demo

Årsaken til at vi trenger en closure er også interessant, vi bruker nå altså call(), som er en litt spesiell funksjon.

JavaScript har i hovedsak tre funksjoner som lar oss kjøre en funksjon og sette en såkalt "this_value" til hva vi vil, samt sende parametere til funksjonen, dette er call(), apply() og bind(), sistnevnte kjører ikke funksjonen, men binder "this" verdien, og ville egentlig vært mye bedre å bruke her, men den er selvfølgelig ikke støttet i IE8, den heller.
Ettersom vi ikke sender noen parametere velger vi å bruke call(), som også tar en liste med parametere, her er et veldig enkelt eksempel på hvordan call() virker, og hvordan det setter verdien av "this" inne i funksjonen vi "caller" :

PHP:
var o = {};

test.call(o, 'Kjell', 'Potetskreller'); // call( this_value, arg1, arg2, arg3, etc );

console.log(o.fornavn); // Kjell


function test(navn, yrke) {
    this.fornavn = navn; // legg merke til at "this" plutselig ble objektet vi sendte inn
}

apply() er nesten likt, men forventer et array som parameter
PHP:
var o = {};

test.apply(o, ['Kjell', 'Potetskreller']); // apply( this_value, [arg1, arg2, arg3]) <- array

console.log(o.fornavn); // Kjell


function test(navn, yrke) {
    this.fornavn = navn; // legg merke til at "this" plutselig ble objektet vi sendte inn
}

Alt i alt så blir det noe sånt, som burde virke i IE8 ?
PHP:
var lenker = document.querySelectorAll('.lenke');

for (var i=0; i<lenker.length; i++) {
    (function(j) {
        lenker[j].attachEvent("onclick", function() {
            myFunc.call(lenker[j]);
        });
    })(i);
}

function myFunc(event) {
    var tall   = this.id.slice(-1);     // siste tegn i ID'en, vil ikke virke over 9
    var text   = this.innerText; // IE8
    var bolle  = document.getElementById('div' + tall);
    var synlig = bolle.style.display;
  
  
    if (text === 'Vis') { // toggle text
        this.innerText = 'Skjul'; // IE8
    } else {
        this.innerText = 'Vis'; // IE8
    }

    if (synlig === 'block') { // toggle visibility
        bolle.style.display = 'none';
    } else {
        bolle.style.display = 'block';
    }
  
    event = window.event || event; // IE har globalt "event" objekt, som vi vil bruke før det lokale
    event.returnValue = false;
}

Siden vil selvfølgelig laste noe tregere med jQuery, ettersom du må laste inn et forholdsvis stort bibliotek, man må ta en avgjørelse på hvor mye de millisekundene teller, og hvor mye man trenger.

Skal man for eksempel ha noe ajax, en del DOM lookups og endringer osv, litt animasjon og slikt, så blir det veldig mye å passe på på egenhånd for å dekke alle nettlesere, og det er enklere å bruke jQuery.
Skal man bare gjøre noen enkle ting, slik som i eksemplet ovenfor, så er det strengt tatt ikke nødvendig med jQuery.

Svært mye utvikling foregår uten jQuery/Angular, faktisk det aller meste, men når det kommer til nettsider og kliensiden, så er nok jQuery fremdeles mye brukt, og du finner det på nesten alle større sider.
Angular er veldig i skuddet om dagen, men mest i one-page apps og blant mange som nettopp begynner med webutvikling som finner Google sine Angular sider.
Med god support og god dokumentasjon på alle ting som har med Angular å gjøre, så er det enkelt å komme i gang med, og at det kommer fra Google hjelper selvfølgelig en del.
Jeg har fremdeles inntrykk av at de fleste utviklere kun velger Angular der det passer, og ikke bruker det til veldig enkle vanlige nettsider, selv om det fint kan brukes der også, men blir noe unødvendig etter min mening.

Den såkalte MEAN stacken (Mongo, Express, Angular, Node) er også forholdsvis populær akkurat nå, selv om C# og Java nok er mest brukt i større bedrifter, og PHP blant de fleste andre, jeg bruker selv MEAN en del, men utelater som regel A'en, altså nettopp Angular, fordi jeg synes det blir enklere uten, og velger i stedet å bruke EJS på serversiden til å genere dynamisk innhold.

Dersom caching er satt opp riktig, så vil selvfølgelig jQuery caches i nettleseren din akkurat som alle andre filer, og en av de enkleste måten å inkludere jQuery på er å bruke Google's CDN, og man kan da anta at mange brukere allerede har den versjonen av jQuery cachet i nettleseren ettersom så mange nettsider bruker nettopp Google's CDN, med samme lenke til filen.
 
Sist redigert:

kongen

kongemedlem
Da er koden testet i IE8 og den fungerer. Takk igjen :)

Hvor mange programmeringsspråk kan du? Det virker som du kan absolutt alt :)
 

adeneo

Medlem
Takk for oppmerksomheten ... tror jeg ?

Min bakgrunn er som hobbyprogrammerar, selvlært er velært, og jeg kan egentlig bare litt javascript og PHP, men er veldig god på å Google !
 
Topp